We have a kotlin/js react project that uses `jsdom...
# coroutines
m
We have a kotlin/js react project that uses
jsdom
via
global-jsdom
. We finally determined that the reason we have tests which never complete in certain repeatable circumstances was that the default coroutines dispatcher was being set to
window.asCoroutineDispatcher()
rather than
NodeDispatcher().
There's currently code in
kotlinx-coroutines-core/js/src/CoroutineContext.kt
which tries to detect if jsdom is in use by looking for a user-agent containing "`jsdom`". We would prefer a way to specify the default dispatcher type without modifying the user-agent since this is requiring specific knowledge of how coroutines works behind the scenes, and it's already changed once from "react-native" to "jsdom". It also seems like the approach in general is hit-or-miss and won't scale well as other situations like mine occur in the future. One approach we used to verify this was our issue was to define a dispatcher override function as well as a function to specifically pick the internal NodeDispatcher.[1] This did work, but does require that we call
setDispatcherOverrideToNodeDispatcher()
before any other code triggers the creation of the default dispatcher. It might be better to instead look for a flag or value in a known location (globalThis.kotlinx.coroutines.config.defaultdispatcher?) which would be set in the environment before kotlin starts executing code. I'm not much of a javascript expert, though. For now, we are manually appending " jsdom" to the current user-agent rather than maintain our own fork of coroutines.
Copy code
Object.defineProperty(globalThis.navigator, "userAgent", {
    value: (globalThis.navigator?.userAgent ?? "Node.js") + " jsdom",
    configurable: true
});
[1] Sample code which provided a hook to change the default dispatcher to NodeTest
Copy code
condensed diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
-internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
+internal actual fun createDefaultDispatcher(): CoroutineDispatcher {
+    dispatcherOverride?.let { return it() }
+
+    return when {
@@ -18,6 +21,7 @@ i
+    }
 }
 
@@ -26,3 +30,13 @@ 
+
+internal var dispatcherOverride: (() -> CoroutineDispatcher)? = null
+
+public fun setDispatcherOverride(block: (() -> CoroutineDispatcher)?) {
+    dispatcherOverride = block
+}
+
+public fun setDispatcherOverrideToNodeDispatcher() {
+    dispatcherOverride = { NodeDispatcher }
+}
🧵 2
o
If you need a specific dispatcher for a set of tests, #C09FQGG85EC, a modern coroutines-first test framework would let you configure this (either globally or at any level of its test element hierarchy). Then everything should be explicit and obvious, without depending on kotlinx-coroutines internals. Would that help?
m
Probably not for our case. We don't use special dispatching for tests as most of our tests are integration tests. Because of that, we don't want to micromanage our async callouts, but just let them run naturally. It'd probably also be a tough sell to convert our hundreds of existing tests to a new test framework, and we're very happy with kotest.
o
Not a sell at all. If you're already on Kotest, you have similar options. And if you're happy, why change?