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.
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
Now let us write some that is intended to test the
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
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):
So this works well for the
Main dispatcher. What about the other ones, like e.g.
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 function
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:
TestDispatcherProvider implements the
DispatcherProvider interface by always providing the given (test-) dispatcher for
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
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:
Being a Dispatcher means:
you will cry a lot, you will laugh a lot.