I'm getting quite stuck with coroutines in one par...
# coroutines
s
I'm getting quite stuck with coroutines in one particular unit test, that only occur once in a while leading to only occasional failed test with error message:
Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used
. I do have the following in my unit tests:
Copy code
@ObsoleteCoroutinesApi
    private lateinit var mainThreadSurrogate: ExecutorCoroutineDispatcher

    @ObsoleteCoroutinesApi
    @ExperimentalCoroutinesApi
    @Before
    fun before() {
        mainThreadSurrogate = newSingleThreadContext("UI thread")
        Dispatchers.setMain(mainThreadSurrogate)
    }

    @ObsoleteCoroutinesApi
    @ExperimentalCoroutinesApi
    @After
    fun tearDown() {
        Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
        mainThreadSurrogate.close()
    }
This only happens on one of many unit tests in the same class, which is in this place: https://github.com/UrbanCompass/Snail-Kotlin/blob/master/snail-kotlin/src/test/java/com/compass/snail/ObservableTests.kt#L221 I'm making this into a kotlin multiplatform project, and running unit tests through the
androidTest
. Any pointers on this please?
z
inject your dispatchers
or better, inject your
CoroutineScope
then use either the scope provided from
runBlocking { }
or
runBlockingTest { }
for unit tests
s
Yeah, I do inject the dispatcher in the KMP version of that source code, which looks like this:
Copy code
observable.debounce(Dispatchers.Default, delayMs = delayMs).subscribe(Dispatchers.Default, next = {
            received.add(it)
        })
Actually I was using
Dispatchers.Main
before
z
No, I mean you should never refer to `Dispatchers.Main`/`Dispatchers.Default`/`Dispatchers.IO` in any class you care about unit testing
☝️ 1
l
Looks like a race condition where some code is run at class initialization, before the
@Before
annotated function.
👍 1
z
it’s a static reference
s
Huh. I do have the initialization of the
Dispatchers
reference initialized in a different class, that gets injected to the class I'm testing. Is that not the way to do Coroutines
Also, I don;t think there is any initialization before the
@Before
annotation here. Here's what I've in setup:
Copy code
private lateinit var subject: Observable<String>
    private lateinit var strings: MutableList<String>
    private var error: Throwable? = null
    private var done: Boolean? = null

    @ObsoleteCoroutinesApi
    private lateinit var mainThreadSurrogate: ExecutorCoroutineDispatcher

    @ObsoleteCoroutinesApi
    @ExperimentalCoroutinesApi
    @Before
    fun before() {
        runBlockingTest {
            mainThreadSurrogate = newSingleThreadContext("UI thread")
            Dispatchers.setMain(mainThreadSurrogate)
            subject = Observable()
            strings = mutableListOf()
            error = null
            done = null
            subject.subscribe(Dispatchers.Unconfined,
                next = { strings.add(it) },
                error = { error = it },
                done = { done = true }
            )
        }
    }
Also, updated to run that unit test in
runBlocking{}
scope, that throws out the same error. Sometimes, the test case, pass, but that error message of using
setMain
still shows up in the log
Think I got it now. No more errors and warnings after using
TestCoroutineDispatcher()
instead of
newSingleThreadContext
simple smile
👍 1
z
referring to a static dispatcher is an anti-pattern
s
Ahh, I think I see what you are saying now @zak.taccardi!
👍 1
z
it just makes testing much more difficult. We have an interface lik
Copy code
interface AppDispatchers {
    val main: CoroutineContext get() = Dispatchers.Main
    val default: CoroutineContext get() = Dispatchers.Default
    val io: CoroutineContext get() = <http://Dispatchers.IO|Dispatchers.IO>
}
This allows for easy testing
s
Yeah, that's the pattern I'm looking to implement now. Makes much more cleaner.
z
For example, idk if you are Android, but you can then create an
EspressoDispatchers()
Copy code
fun EspressoDispatchers() : AppDispatchers = object : AppDispatchers {
    override val default = AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
    override val io = AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
}
and definitely inject your
CoroutineScope