Testing Coroutines — Dispatchers

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.

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 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 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 that makes sure that the UI code is executed on the dispatcher:

Now let us write some that is intended to test the 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 function which allows to set our own dispatcher as main. In order to benefit from all goodies we will use the provided by :

Now our test becomes green. In case you are wondering where that 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 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 , or call on the dispatcher (which is effectively the same thing):

Other Dispatchers

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

We could easily write a test like we did before, but how can we inject our test dispatcher? Is there a 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 , an interface that abstracts the direct usage of dispatchers. To do so, we have to change our 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 implements the interface by always providing the given (test-) dispatcher for , , 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 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 :

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. directly. For testing it has the convenience function : an extension of the original which automatically adds the 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

Developer @ https://www.compeople.de. Married, two children. Playing bass guitar.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store