Mastering Coroutines. Android. Unit Tests

This article doesn’t cover basic of coroutines usage. If you’re not familiar with them I can advice to read intro at kotlinx git repo.

Post describes difficulties when writing unit tests for code that use coroutines. Also in the end we show the solution for this problem.

Imagine that we have simple MVP architecture in app. Activity looks like this:

In presenter we use coroutines for async operations. Repository just emulates long running operation.

All works well, but now we need to test this code. Although we inject all dependencies explicit using constructor it is not easy to test our code. We use mockito library for testing. Also we need to add runBlocking function usage to have ability work with coroutines and suspended functions.

After running test we see that it fails because of:

org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class sample.dev.coroutinesunittests.ContentRepository
Mockito cannot mock/spy because :
— final class

We need to add open keyword to ContentRepository class and to function requestContent()

After running test we see that it fails once again. Now it happened because UI coroutine context use looper under the hood:

Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked

I find post about similar problem. You can see it here.

Author solve this problem moving logic of running coroutines to Activity.

Here is another solution: pass Coroutines context explicit using Presenter constructor and then use this context for starting coroutines. We need to create class CoroutineContextProvider:

It just has two fields that has reference to same context as we used in code earlier. It should be open and fields must have open modifier to have opportunity to inherit this class and override it’s fields values for testing purpose. Also we need to use initialization by lazy to initialize value only when they used first time.

Now we should add usage of this class to presenter:

The last step is to create TestContextProvider and add it’s usage in test:

We use Unconfied context. It means that coroutines execute at the same thread where other code running. It is similar to Trampoline` scheduler in RxJava

Our last step is to pass TestContextProvider to presenter constructor in test:

That’s all. Test will be green after next launch.

Talk is cheap. Show me the code. © Linus Torvalds

Link to github.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store