Testing Coroutines — Dispatchers

Ralf Stuckert
4 min readMar 24, 2020

--

Image by dmpcreate from Pixabay

In part one and two of this series we saw how to make use of the time control features of the kotlinx-coroutines-test module. In this part you will see how to test code that utilizes a dedicated dispatcher.

Update: This article describes the old API. With 1.6.0 significant changes has been introduced which are described here. Over the next few weeks I will update this article and the examples in order to provide some more details on the new API, stay tuned.

Main Dispatcher

UI code like e.g. Android, Swing, JavaFX is executed by a dedicated UI-Thread. When using coroutines you have to use the Main dispatcher for that purpose. But how do we test this code? We could integrate a dedicated platform main dispatcher in our tests, but that is not always feasible. Moreover, those neat time control features introduced in the first two parts would not be accessible. Luckily, the coroutine test module provides functionality to fill in this gap. To demonstrate this, we will introduce a function waitForUserConfirm() that is supposed to be UI code:

In order to call it from anywhere in our code — where anywhere means: regardless of the current dispatcher — we have a function confirmDone() that makes sure that the UI code is executed on the Main dispatcher:

Now let us write some that is intended to test the confirmDone() function:

We set up our UI mock to delay a bit (our user is not the fastest ;-) and return OK. Run the test: As already said, if we do not provide any main dispatcher this test will complain:

java.lang.IllegalStateException: Module with the Main dispatcher is missing. 
Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'
and ensure it has the same version as 'kotlinx-coroutines-core'

To fix this, we will use the setMain() function which allows to set our own dispatcher as main. In order to benefit from all goodies we will use the TestCoroutineDispatcherprovided by runBlockingTest():

Now our test becomes green. In case you are wondering where that testDispatcher is coming from: this is a tiny custom helper function, hopefully there will something "official" soon, see issue 1609.

Main Dispatcher JUnit Extension

To ease the handling of using a test dispatcher for main, you could also use a JUnit rule resp. extension. Here is a variant of a JUnit 5 extension that creates a TestCoroutineDispatcher for each test, sets it as the main dispatcher, and provides it as a parameter for the test. You have to use this dispatcher either by passing it as a context to runBlockingTest(), or call runBlockingTest() on the dispatcher (which is effectively the same thing):

Other Dispatchers

So this works well for the Main dispatcher. What about the other ones, like e.g. Default or IO? Let's do this by example: we will take our already known UserService that loads a user from e.g. a database. In order to make sure that this is done via IO dispatcher, we have a functionloadUserIO():

We could easily write a test like we did before, but how can we inject our test dispatcher? Is there a setIO() function? Nope. Maybe there will be one in future releases (see issue 1365), but not at the time of this writing.

A common approach is to inject a DispatcherProvider, an interface that abstracts the direct usage of dispatchers. To do so, we have to change our loadUserIO() as well:

Now for production code, we have to use a provider that utilizes the real dispatchers, but we can inject the test dispatcher in our unit tests:

So this TestDispatcherProvider implements the DispatcherProvider interface by always providing the given (test-) dispatcher for main, io, etc. Since this idea is widespread, there are already some libraries out there doing the job. I'm using this neat library here. Besides providing a production and test dispatcher provider, it can implicitly provide the DispatcherProvider in the coroutine context, and has some extension functions to access it. The advantage is, that you do not have to alter your signature in order to pass the provider, which eases migration from direct usage of the Dispatchers:

But how is the provider injected into the coroutine context? For production code you don’t have to do anything. If you access the provider, and there is none yet, the lib will instantiate the default provider that simply provides the orginal dispatchers, like you were using e.g. Dispatchers.IOdirectly. For testing it has the convenience function runBlockingTestProvided(): an extension of the original runBlockingTest() which automatically adds the TestDispatcherProvider to the context of the test scope:

That’s it for today, the last part will be on pausing the dispatcher and handling exceptions. As already mentioned, you will find all example in the accompanying git repository.

Being a Dispatcher means:
you will cry a lot, you will laugh a lot.
Anonymous

--

--