azabost
12/12/2024, 12:51 PMkotlinx-coroutines-test
used in tests
• JUnit @Rule
with Dispatchers.setMain
◦ with a UnconfinedTestDispatcher()
in its constructor
In practice, something strange happens
It turns out that during the construction of UnconfinedTestDispatcher
there is a call to Dispatchers.getMain
(!), which is invoked before we get a chance to call setMain
(because we're only trying to construct a dispatcher first)
The getMain
invocation goes to kotlinx-coroutines-android
which is not needed in unit tests but we get it transitively e.g. via org.jetbrains.kotlinx:kotlinx-coroutines-bom
that is included by many libraries such as implementation "androidx.lifecycle:lifecycle-runtime"
so we accidentally get it in our test classpath
which then tries to Looper.getMainLooper()
, and that obviously fails
This is the callstack (more or less):
1. UnconfinedTestDispatcher
2. TestMainDispatcher.currentTestScheduler
3. TestMainDispatcher.currentTestDispatcher
4. Dispatchers.Main
(getter)
5. MainDispatcherLoader.loadMainDispatcher
6. MainDispatchersKt.tryCreateDispatcher
7. kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher
8. Looper.getMainLooper()
If I get rid of kotlinx-coroutines-android
dependency by hand then the unit tests work fine
configurations.all {
exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-android")
}
But I've never had to do that in any other project I participated in so I presume it's not the intended way to solve this problem
Has anyone seen a similar issue?Dmitry Khalanskiy [JB]
12/12/2024, 12:56 PMUnconfinedTestDispatcher()
with UnconfinedTestDispatcher(TestCoroutineScheduler())
, does the issue persist?azabost
12/12/2024, 12:58 PMazabost
12/12/2024, 1:03 PMazabost
12/12/2024, 1:04 PMazabost
12/12/2024, 1:22 PMdependencyInsight --configuration debugUnitTestCompileClasspath --dependency org.jetbrains.kotlinx:kotlinx-coroutines-core
before and after adding the explicit org.jetbrains.kotlinx:kotlinx-coroutines-core
dependency and I can't see significant changes in the output
so, to be honest, I'm not sure why adding it also seems to solve the problemazabost
12/12/2024, 1:23 PMDmitry Khalanskiy [JB]
12/12/2024, 1:24 PMThis shouldn't be the case, becausethat is included by many libraries such asorg.jetbrains.kotlinx:kotlinx-coroutines-bom
so we accidentally get it in our test classpathimplementation "androidx.lifecycle:lifecycle-runtime"
-bom
ensures that all dependencies that are present anyway have the same version (see https://docs.gradle.org/current/userguide/platforms.html#sec:regular-platform). It shouldn't add all dependencies on its own. Something else must be adding kotlinx-coroutines-android
.
On the coroutines side, the code does say that the main dispatcher gets initialized when you call UnconfinedTestDispatcher()
, but it's actually not necessary. If it's an issue, I can fix this in the next release, and the Android dispatcher is not going to get created.
so, to be honest, I'm not sure why adding it also seems to solve the problemYeah, me neither. Maybe it's not a clean build and something got incorrectly cached?
azabost
12/12/2024, 1:29 PMMaybe it's not a clean build and something got incorrectly cached?Yes, it's not a clean build, but I'm switching between these two states (with and without the explicit dependency) and I get consistent results (success and failures) so I wouldn't blame cache for that unless I'm being too naive 🤔
This shouldn't be the case, becauseThanks for explaining the thing with BOM. Actually, that's how I expected it to work. There were more dependencies and I copy-pasted the first I noticed without thinking too much about it. Looks like another dependency must have included coroutines-android more directly.ensures that all dependencies that are present anyway have the same version-bom
If it's an issue, I can fix this in the next release, and the Android dispatcher is not going to get created.If initializing the main dispatcher is not necessary for constructing the test dispatcher, then maybe it would be better
azabost
12/12/2024, 1:31 PMsetMain
and you are told that the problem is ... you didn't call setMain
🤯Dmitry Khalanskiy [JB]
12/12/2024, 1:32 PMIf initializing the main dispatcher is not necessary for constructing the test dispatcher, then maybe it would be betterOk, do you want to file the issue (https://github.com/Kotlin/kotlinx.coroutines/issues), or should I do it?
azabost
12/12/2024, 1:33 PMazabost
12/12/2024, 9:35 PMazabost
12/12/2024, 10:13 PMUnconfinedTestDispatcher(TestCoroutineScheduler())
), one test was getting "fixed" but another one contained a suppressed exception in its stacktrace pointing to the same kind of problem.azabost
12/12/2024, 10:39 PMazabost
12/12/2024, 10:45 PMazabost
12/12/2024, 10:47 PM