Technical blog from Craig Russell.
This articles describes the provided coroutine test dispatchers, standard and unconfined, the difference between them, and when to use each
A coroutine dispatcher
determines which thread (or thread pool) should be used for running the coroutine. You are likely familiar with standard dispatchers, such as Dispatchers.IO
and Dispatchers.Default
(and Dispatchers.Main
if working in Android). When using these, we can ensure that a coroutine is performed on the correct type of thread (e.g., threads meant for heavy CPU work, or IO work, or main thread etc…)
Coroutine Dispatchers determine what thread or threads the corresponding coroutine uses for its execution. The coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined.
- Source: kotlinlang
launch(Dispatchers.Default) {
// do work that might be computationally expensive for the CPU
// uses a thread pool
// - typically sized to have one thread for every CPU core the device has
// - minimum of 2 threads
}
launch(Dispatchers.IO) {
// do IO work, like reading from disk or sending data over the network
// uses a thread pool
// - the number of threads can grow and shrink on demand up to a pre-configured max size
// - default max thread pool is 64
}
launch (Dispatchers.Main) {
// runs on the Android main thread. e.g., used for when interacting with Views
}
When writing unit tests, it is recommended to use a TestDispatcher
instead of a real coroutine dispatcher.
Doing so allows you to have more control over how and when coroutines are launched in your classes under test, and can therefore result in more deterministic and reliable tests. You can control time, like skipping calls to delay
to make tests run faster than they otherwise would, or advancing virtual time forward by a certain amount and asserting on the state of your coroutines at that moment.
In order to dispatch coroutines using a test dispatcher, you should inject coroutine dispatchers.
If you’ve read the above, you now know:
What remains now is deciding which of the two available test dispatchers to use. Let’s see what the options are:
A standard test dispatcher does not execute any tasks automatically. When coroutines are launched with this dispatcher, instead of executing immediately they are left in a pending state.
This gives you full control over what is executed and when, so that you use that fine control to assert your code is working as expected.
You can access the TestCoroutineScheduler
linked to the StandardTestDispatcher
which provides methods for controlling the execution state of pending tasks. The following functions can be called directly from inside your runTest
block.
An unconfined test dispatcher offers no guarantees on the order in which coroutines will be launched.
In return for giving up this control, however, you are not required to manually call runCurrent()
and advanceUntilIdle()
in your tests as coroutines are eagerly launched with this dispatcher.
Using this TestDispatcher can greatly simplify writing tests where it’s not important which thread is used when and in which order the queued coroutines are executed. Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without going through a dispatch; this can be helpful for testing Channel and StateFlow usages. (Source)
Test Dispatcher Type | Full control over execution order | Coroutines Run Automatically |
---|---|---|
Standard | ✅ | ❌ |
Unconfined | ❌ | ✅ |