Unit testing kotlin coroutines on Android with JUnit 5

Photo by Franck V. on Unsplash

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.

JUnit 4

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.setMain() and Dispatchers.resetMain() before/after each test:

JUnit 4 rule that replaces Dispatchers.Main with TestCoroutineDispatcher

JUnit 5

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:

JUnit 5 extension that replaces Dispatchers.Main with TestCoroutineDispatcher

JUnit 5 beforeEach(ExtensionContext) and afterEach(ExtensionContext) are equivalent to JUnit 4 starting(Description) and 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:

Example unit test that uses CoroutinesTestExtension

More about JUnit 5 extensions here: https://junit.org/junit5/docs/current/user-guide/#extensions

Bonus: Power of time machine

Notice that 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

coroutinesTestExtension.pauseDispatcher()
coroutinesTestExtension.resumeDispatcher()
coroutinesTestExtension.advanceTimeBy(delayTimeMillis)
coroutinesTestExtension
.advanceUntilIdle()

Rx fans will notice similarities to the API of RxTestScheduler.

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.

P.S.

If you want to migrate your unit tests from JUnit 4 to JUnit 5, replace the JUnit 4 dependency with

testImplementation "org.junit.jupiter:junit-jupiter:$junit_jupiter_version"

and replace your JUnit 4 imports with JUnit 5, e.g.:

// replace
import
org.junit.Test
// with
import org.junit.jupiter.api.Test

Happy coding!

--

--

Self-Employed Senior Android Dev | https://romantikonov.com/

Love podcasts or audiobooks? Learn on the go with our new app.

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