Verifying suspending functions with Mockito (or alternatives)


#1

I’ve run into trouble using Mockito to verify invocations of suspending functions. This appears to be related to how suspending functions get translated for the JVM, which I can at least determine involves an additional parameter. Because Mockito checks all the arguments passed to the mock’s Java methods, the verification fails, since the last argument from the invocation does not match that from the verification.

Here is a simple example:

interface Suspendable {
    suspend fun suspendFunction()
}

class CallsSuspendable(val suspendable: Suspendable) {
    fun callSuspendable() {
        runBlocking {
            suspendable.suspendFunction()
        }
    }
}

@Test
fun testCallingSuspendable() {
    val mockSuspendable = Mockito.mock(Suspendable::class.java)
    val callsSuspendable = CallsSuspendable(mockSuspendable)

    callsSuspendable.callSuspendable()

    runBlocking {
        Mockito.verify(mockSuspendable).suspendFunction()
    }
}

In this example, I get the error:

Argument(s) are different! Wanted:
interface.suspendable(
(testCallingSuspendable$1) kotlinx.coroutines.experimental.CoroutineScope.() -> kotlin.Unit
);
-> at MockSuspendFunctionTest$testCallingSuspendable$1.doResume(MockSuspendFunctionTest.kt:38)
Actual invocation has different arguments:
interface.suspendable(
(useSuspendable$1) kotlinx.coroutines.experimental.CoroutineScope.() -> kotlin.Unit
);
-> at MockSuspendFunctionTest$UsesSuspendable$useSuspendable$1.doResume(MockSuspendFunctionTest.kt:25)

Any other ideas how to verify invocation of a suspend function? I imagine there may be some kind of tests like this in the Kotlin project itself.


#2

I don’t think there is any kind of good solution to this problem, but to teach Mockito to understand Kotlin suspending functions. There are many other problems beyond verification. Mockito matchers don’t work properly either, due to this extra hidden parameter. It looks like it requires a patch to Mockito itself.

The only only workaround I can see is this.

First, you’ll have to define another interface that defines the corresponding functions as non-suspending:

interface SuspendableMock {
    fun suspendFunctionMock()
}

Then you’ll have to create an implementation that implements your suspendable interface with delegation to the above non-suspendable interface:

class SuspendableImpl(val mock: SuspendableMock) : Suspendable {
    suspend override fun suspendFunction() = mock.suspendFunctionMock()
}

Then you can mock SuspendableMock with Mockito normally (the following test passes):

@Test
fun testCallingSuspendable() {
    val mockSuspendable = Mockito.mock(SuspendableMock::class.java)
    val callsSuspendable = CallsSuspendable(SuspendableImpl(mockSuspendable))

    callsSuspendable.callSuspendable()

    runBlocking {
        Mockito.verify(mockSuspendable).suspendFunctionMock()
    }
}

#3

Well, that’s certainly better than making my own mocking library from scratch to support suspending functions. If you’ve got a bunch of non-suspending functions in your interface as well, class delegation saves you at least a little bit of boilerplate over manually delegating every function call:

interface Suspendable {
    suspend fun suspendFunction()
    fun blockingFunction1()
    fun blockingFunction2()
}

interface SuspendableMock {
    fun suspendFunction_()
    fun blockingFunction1()
    fun blockingFunction2()
}

class SuspendableImpl(val mock: SuspendableMock) : Suspendable, SuspendableMock by mock {
    suspend override fun suspendFunction() = mock.suspendFunction_()
}

#4

I’ve also submitted a pull request for Mockito that fixes this problem: https://github.com/mockito/mockito/pull/1032

Hopefully, it is going to be accepted into the next version.


#5

Very cool, thank you.


#6

I saw your changes were merged in to Mockito. Thanks for taking care of that.