Mocking Coroutines

A few posts out there guide us to test Coroutines. The issue is that many posts where written before the API became stable, so before the scoping changes that happened. Also, I did not find many tips about mocking and Coroutines. Time to fill this gap.
Deferred
A typical use case for Coroutines is the API layer, especially with Retrofit. You probably have something like:
interface RetrofitApiService {
@GET("v2/LiveGames")
fun getLiveGames(): Deferred<List<ApiGame>>
}
If we think about testing, we probably need to replace this with some fake implementation. Mocking is easiest to do using Mockito. We’ll use Mockito Kotlin, which adds some syntactical sugar on top.
Without previous experience we already encounter our first problem when trying to stub out the API method:
val liveGames = listOf(...)
val service = mock<RetrofitApiService> {
on { getLiveGames() } doReturn liveGames
}
This does not compile as we need to return a Deferred<List>.
To solve this you have to create a Coroutine. The easiest way to do this is to use the async
function:
val service = mock<RetrofitApiService> {
on { getLiveGames() } doReturn GlobalScope.async { liveGames }
}
To make this a bit nicer to ready, let’s hide this behind a function similar to toSingle()
from RxKotlin:
fun <T> T.toDeferred() = GlobalScope.async { this@toDeferred }
This lets us write it in the following way:
val service = mock<RetrofitApiService> {
on { getLiveGames() } doReturn liveGames.toDeferred()
}
As Deferred
is just a type, there is nothing special needed to verify a call:
@Test
@Suppress("DeferredResultUnused")
fun `calls getLiveGames on retrofit service`() {
verify(service).getLiveGames()
}
Edit: Kenji Abe and John O'Reilly pointed out that CompletableDeferred
is an even better solution:
fun <T> T.toDeferred() = CompletableDeferred(this)
Suspend
When looking back at the application, a typical pattern is to hide the Retrofit behind some other layer that uses suspend
functions:
class Api {
suspend fun getLiveGames(): List<ApiGame> =
service.getLiveGames().await()
}
One of the nice things with Coroutines is the fact that it hides a lot of the complexity of threading and asynchrony. Except for the keyword suspend
, this looks like is a synchronous function with a direct return type.
Let’s get back to Mockito and mock this layer:
The problem is that every call to getLiveGames
needs to be surrounded by a runblocking
.
We could write something like:
val api = mock<API>()
runBlocking {
`when`(api.getLiveGames().thenReturn(liveGames)))
}
But as you saw earlier, I prefer the Mockito Kotlin way of doing things. Good for us that Niek already added Coroutines support:
val api = mock<API> {
onBlocking { getLiveGames() } doReturn liveGames
}
Let’s take a look into the implementation:
fun <T : Any, R> KStubbing<T>.onBlocking(m: suspend T.() -> R)
: OngoingStubbing<R> {
return runBlocking { Mockito.`when`(mock.m()) }
}
The thing to remember is, that you can use the suspend keyword also when passing lambdas and functions around. This is important so that the compiler can validate you use Coroutines the right way. If you don’t use Kotlin Mockito, you can write something similar for your usage now.
When it comes to verification on a suspend function, the compiler forces us to use the runBlocking
as well:
runblocking {
verify(api).getLiveGames()
}
Niek also added a nice helper to hide this:
verifyBlocking(api) { getLiveGames() }
You probably have an idea by now how this is implemented under the hood:
fun <T> verifyBlocking(mock: T, f: suspend T.() -> Unit) {
val m = Mockito.verify(mock)
runBlocking { m.f() }
}
The end
As you can see testing functionality, that is built with Coroutines adds a bit of complexity:
suspend
functions can’t just be called from a test like a normal one- Mocking and programming your mock is a bit harder.
Be aware of those, try to remember the tips from this article in mind and keep testing!
PS: and maybe try to use less mocking, but that is another story ;)