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.
Typical Android Architecture
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.
Unit test
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.
CoroutineContextProvider and explicit usage.
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