Unit testing kotlin coroutines on Android with JUnit 5
JUnit 5 extensions vs JUnit 4 rules
If you unit test coroutines in your Android app, you have already seen this exception:
Exception in thread "main @coroutine#1" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used
For those of you who use or plan to use JUnit 5 in your projects, I’ll show how to write a JUnit 5 test extension that fixes this problem.
We know what caused the exception above:
Dispatchers.Main is not available in unit tests because it relies on
Looper.getMainLooper() from Android SDK. As the hint in the message says, you can fix it by mocking
Dispatchers.Main , and there are articles out there explaining how to do it with JUnit 4. For example, here is a solution for JUnit 4 that invokes
Dispatchers.resetMain() before/after each test:
If you are using JUnit 5 / Jupiter in your project, the solution above won’t work. A concept of rules was deprecated in JUnit 5 in favour of a new concept of test extensions. Let’s convert the rule above to a JUnit 5 extension:
afterEach(ExtensionContext) are equivalent to JUnit 4
finished(Description) : they are called before and after each test method annotated with
@Test . The extension above will mock
Dispatchers.Main before each test and reset the mock afterwards.
And now let’s add this extension to a test class:
More about JUnit 5 extensions here: https://junit.org/junit5/docs/current/user-guide/#extensions
Bonus: Power of time machine
CoroutinesTestExtension from above implements
TestCoroutineScope interface (by delegating it to a
TestCoroutineScopeImpl that delegates it to the same
TestCoroutineDispatcher instance that replaces
Dispatchers.Main). It means that you can control the execution of coroutines on
Dispatchers.Main by calling
Rx fans will notice similarities to the API of Rx
Note that this does not affect coroutines running on other dispatchers, e.g.
Dispatchers.IO . If your class under test uses other dispatchers, consider passing them as a constructor argument and mocking them with a
TestCoroutineDispatcher in unit tests.
If you want to migrate your unit tests from JUnit 4 to JUnit 5, replace the JUnit 4 dependency with
and replace your JUnit 4 imports with JUnit 5, e.g.: