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.


#7

Hi there,

I’m having a similar issue that happens when verifying suspending functions that end up yielding. Follows is a simple example:

import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.runBlocking
import org.junit.jupiter.api.Test
import org.mockito.Mockito

class SuspendTest {
    class Suspendable {
        suspend fun suspendFunction() {
            delay(1)
            async { delay(1) }.await()
        }
    }

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

        }
    }

    @Test
    fun testCallingSuspendable() = runBlocking<Unit> {
        val suspendable = Suspendable()
        val verifySuspendable = Mockito.spy(suspendable)
        val callsSuspendable = CallsSuspendable(verifySuspendable)

        callsSuspendable.callSuspendable()

        Mockito.verify(verifySuspendable, Mockito.times(1)).suspendFunction()
    }
}

When I run this, I get

org.mockito.exceptions.verification.TooManyActualInvocations: 
suspendable.suspendFunction();
Wanted 1 time:
-> at SuspendTest$Suspendable.suspendFunction(SuspendTest.kt:11)
But was 2 times:
-> at SuspendTest$CallsSuspendable.callSuspendable(SuspendTest.kt:19)
-> at SuspendTest$Suspendable$suspendFunction$1.doResume(SuspendTest.kt)

We are trying to write an integration test that spies on the real coroutines so they end up yielding and Mockito interprets the resume as another invocation.

Any ideas on how to achieve this, or if this behaviour is fixable in Mockito ? Thanks!