craigrussell

Technical blog from Craig Russell.

 

StandardTestDispatcher vs UnconfinedTestDispatcher

This articles describes the provided coroutine test dispatchers, standard and unconfined, the difference between them, and when to use each

Some background

What is a coroutine dispatcher?

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.

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
}

What’s the relevance of coroutine dispatchers when unit testing?

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.

Which test dispatcher to use?

If you’ve read the above, you now know:

  1. why a test dispatcher is useful
  2. how to inject one into your class under test

What remains now is deciding which of the two available test dispatchers to use. Let’s see what the options are:

StandardTestDispatcher

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.

If coroutines don’t automatically run when using StandardTestDispatcher, how do you make them run?

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.

runCurrent()

advanceUntilIdle()

advanceTimeBy(ms)

UnconfinedTestDispatcher

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)

Comparing standard and unconfined test dispatchers

Test Dispatcher Type Full control over execution order Coroutines Run Automatically
Standard
Unconfined

Links

Home