Reputation: 3260
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
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
Reputation: 2420
When you need to
on .... do return ..
and method is suspended, with mockito you can use this:
then:
myObject.stub {
onBlocking { isFoo() }.doReturn(true)
}
Upvotes: 30
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