Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 952
Mircea Nistor Avatar asked Aug 03 '18 13:08

Mircea Nistor


People also ask

How suspend functions work Kotlin?

A suspending function is simply a function that can be paused and resumed at a later time. They can execute a long running operation and wait for it to complete without blocking. The syntax of a suspending function is similar to that of a regular function except for the addition of the suspend keyword.

Can coroutines be suspended and resumed?

If you notice the functions closely, they can be used to resume the coroutine with a return value or with an exception if an error had occurred while the function was suspended. This way, a function could be started, paused, and resume with the help of Continuation. We just have to use the suspend keyword.

How do you wait for Suspend to finish Kotlin?

To wait for a coroutine to finish, you can call Job. join . join is a suspending function, meaning that the coroutine calling it will be suspended until it is told to resume. At the point of suspension, the executing thread is released to any other available coroutines (that are sharing that thread or thread pool).

What is suspend modifier in Kotlin?

The Kotlin compiler transforms every suspend function to be a state machine, which optimises using callbacks every time a function needs to suspend. Knowing what the compiler does under the hood now, you can better understand why a suspend function won't return until all the work that it started has completed.


2 Answers

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
like image 113
Adib Faramarzi Avatar answered Oct 15 '22 12:10

Adib Faramarzi


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)
}
like image 30
Vahab Ghadiri Avatar answered Oct 15 '22 11:10

Vahab Ghadiri