ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Mocking Coroutines

Mockingbirds © https://www.flickr.com/photos/emdot/464805199

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 ;)

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Danny Preussler

Android @ Soundcloud, Google Developer Expert, Goth, Geek, writing about the daily crazy things in developer life with #Android and #Kotlin

Responses (4)

Write a response