Kotlin coroutine unit test fails with "Module with the Main dispatcher had failed to initialize"
Solution 1
You don't have access to Dispatchers.Main in unit testing
See https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/
Dispatchers.Main Delegation
part explains in detail what you need to do.
Solution 2
When running tests e.g for ViewModel that launch coroutines you are most likely to fall into the following exception
java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests, Dispatchers.setMain from kotlinx-coroutines-test module can be used
The reason behind this is the lack of Looper.getMainLooper()
on the testing environment which is present on a real application. To fix this you need to swap the Main dispatcher with TestCoroutineDispatcher
Make sure you have a coroutine-test dependency on your Gradle file
"org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version"
SOLUTION 1 - Not scalable
Define the following on your test class -> Annotate your class with @ExperimentalCoroutinesApi
val dispatcher = TestCoroutineDispatcher()
@Before
fun setup() {
Dispatchers.setMain(dispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
Note: You can also pass Dispatchers.Main
as constructor dependency for your repositories as CoroutineDispatcher
in case you have one. It is recommended not to hardcode your dispatchers on repositories/viewmodels etc WATCH-THIS PLEASEEEEEEEE
Why not scalable: You will need to copy and paste the same code on each test class
SOLUTION 2 - Scalable [Use This - It is used by Google]
In this solution, you create the custom rule. Add a utility class on your test package
@ExperimentalCoroutinesApi
class MainCoroutineRule(
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher(), TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
If you want explanations on the utility class above, refer to this CODE-LAB
On your test class just add this the following lines and you will be good to go
@get:Rule
val coroutineRule = MainCoroutineRule()
I think you can see why this is scalable if you have a lot of test classes
SOLUTION 3 [I hope you don't reach here]
You can also use Dispatchers.Unconfined
LINK
A coroutine dispatcher that is not confined to any specific thread. It executes the initial continuation of a coroutine in the current call-frame and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid stack overflows
You can add it as follows
@Before
fun setup() {
Dispatchers.setMain(Dispatchers.Unconfined)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
Happy coding . . . .
Solution 3
In my case I had set the main coroutines dispatcher for unit testing and still saw some errors of that from time to time.
I've added to the build.gradle as in here:
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
and I don't see that error anymore.
Solution 4
now you can add this to your test :
Dispatchers.setMain(Dispatchers.Unconfined)
or other dispatcher.. it's experimental but it works!
Solution 5
This is thrown because Dispatcher.Main is missing. It is Android based and therefore cannot be used for unit tests. The solution lies in the documentation by the Coroutines team. Below is an example that solved my problem and is included in the documentation.
class SomeTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
@Test
fun testSomeUI() = runBlocking {
launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher
// ...
}
}
}
What you should note is newSingleThreadContext("UI Thread")
and Dispatchers.setMain(mainThreadSurrogate)
which is called before any tests in order to create a Main Dispatcher.
takharsh
I am a passionate developer, with more than 6 years of experience in Mobile Application Development, Mobile Data Security, SDK/Libraries development especially in Mobility Domain. I like to work in an environment where I can learn and grow my skills with a team and as organization where I can show my capabilities, thoughts and creativity. Like to talk about new technologies, coding techniques and everything which is happening around the globe. "Blue planet is awesome place to live!!"
Updated on June 16, 2022Comments
-
takharsh almost 2 years
While running unit test for kotlin suspend method which uses
withContext(Dispatchers.Main)
the test method fails with below exception:My coroutine lib versions are kotlinx-coroutines-core:1.1.1 and kotlinx-coroutines-android:1.1.1
Example:
suspend fun methodToTest() { withContext(Dispatchers.Main) { doSomethingOnMainThread() val data = withContext(Dispatchers.IO) { doSomethingOnIOThread() } } }
Also, when I remove the
withContext(Dispatchers.Main)
it works fine.java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:79) at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:54) at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:373) at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25) at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:152) at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
-
takharsh over 4 yearsIt needs mocking of main Thread using Dispatchers.setMain(mainThreadSurrogate). Also need to add kotlinx-coroutines-test as dependency
-
Tzegenos about 3 yearsThe best answer. The only one that worked for me! Thanks!
-
Sira Lam about 3 yearsNice answer, given the context of unit testing, do you think solution 3 is a simple and good enough approach when compared to solution 2?
-
Admin over 2 yearsAdding a reference to the documentation could be helpful for future references. kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test