Mircea Nistor
Mircea Nistor

Reputation: 3260

In kotlin, how do I mock a suspend function that wraps a callback?

Let's say there's an interface with a callback:

interface SomeInterface {
    fun doSomething(arg: String, callback: (Exception?, Long) -> Unit)
}

which I extend into a suspend function like this:

suspend fun SomeInterface.doSomething(arg: String): Long = suspendCoroutine { cont ->
    this.doSomething(arg) { err, result ->
        if (err == null) {
            cont.resume(result)
        } else {
            cont.resumeWithException(err)
        }
    }
}

I'd like to mock this in tests, but am failing. Ideally I'd like to use something like this:

@Test
fun checkService() {
    runBlocking {
        val myService = mock<SomeInterface>()
        whenever(myService.doSomething(anyString())).thenReturn(1234L)
        val result = myService.doSomething("")
        assertEquals(result, 1234L)
    }
}

The above syntax fails with a mockito exception because it's expecting a matcher for the callback.

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:

How can I mock a suspend function like that? If a similar syntax is not possible how can I have the mock call back with the desired arguments such that the suspend variant that is used throughout my code returns the desired result during tests?

Update: It seems it's not possible when it's an extension function. Based on Marko Topolnik's comment, I gather it's because an extension is simply a static function which is out of mockito's capability.

When the suspend function is a member function, then it works as expected, with my original syntax.

Here is a gist with some demo code: https://gist.github.com/mirceanis/716bf019a47826564fa57a77065f2335

Upvotes: 25

Views: 26488

Answers (3)

antken
antken

Reputation: 989

Here is a simple solution: create a utility function and call it when you mock:

inline fun <reified T> anyNonNull(): T = Mockito.any(T::class.java)

Then you can use it in your code like this:

Mockito.`when`(myService.doSomething(anyNonNull<String>())).thenReturn(1234L) 

Upvotes: 0

Vahab Ghadiri
Vahab Ghadiri

Reputation: 2420

When you need to

on .... do return ..

and method is suspended, with mockito you can use this:

  • use this lib mockito-kotlin
  • Mock your object, lets name it myObject (myObject has suspend method named isFoo)

then:

 myObject.stub {
    onBlocking { isFoo() }.doReturn(true)
}

Upvotes: 30

Adib Faramarzi
Adib Faramarzi

Reputation: 4062

I suggest using MockK for your tests, which is more coroutine-friendly.

To mock a coroutine, you can use coEvery and returns like below:

val interf = mockk<SomeInterface>()
coEvery { a.doSomething(any()) } returns Outcome.OK

Upvotes: 29

Related Questions