Testing Coroutines — Introduction

Ralf Stuckert
3 min readMar 11, 2020

--

Photo by Taylor Smith on Unsplash

This is the first part of my little series on testing coroutines, where we will explore the capabilities of the kotlinx-coroutines-test module. All examples (and some utility functions) are provided in a Git repository, so give it a try. This first part will introduce the building blocks of the module, and give some examples on eager execution and virtual time.

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.

Building Blocks

The kotlinx-coroutines-test module consists of four ingredients: The TestCoroutineDispatcher, a TestCoroutineExceptionHandler, the TestCoroutineScopeand finally the runBlockingTest() function. Let's take them step by step.

TestCoroutineDispatcher

In contrast to other dispatchers, this one executes new coroutines immediately like they where started in mode UNDISPATCHED. This eases the handling in tests a bit. But you can change this behaviour, either by providing a dedicated start mode, or by pausing the dispatchers.

Another important point is, that it is up to a dispatcher to implement the concept of time on which scheduling and delay is based on. The TestCoroutineDispatcherimplements a virtual time and gives you fine grained control on it. This allows to write robust time based tests.

TestCoroutineExceptionHandler

A CoroutineContext may contain a CoroutineExceptionHandler which is comparable to the uncaught exception handler on threads, and is intended to handle all exceptions that arise in a coroutine. This implementation captures and collects all exceptions, so they can be inspected in tests, and rethrows the first one on cleanup.

TestCoroutineScope

This scope provides a TestCoroutineDispatcher and TestCoroutineExceptionHandler by default, if none is already given in the context. It also provides access to the the time controlling functions like advanceTime... and the uncaughtExceptions by delegating them to the TestCoroutineDispatcher resp. TestCoroutineExceptionHandler.

runBlockingTest

This variant of runBlocking() ties everything up and provides you a TestCoroutineScope and therefore a TestCoroutineDispatcher and TestCoroutineExceptionHandler. It advances time of the test dispatcher until idle, which effectively makes sure that everything in the test block is run. It also checks for misusage by counting the active jobs before and after the test.

Eager Execution

As already told the TestCoroutineDispatcher executes coroutines eagerly, means there is no need to e.g. join a job. For comparison let's first see the old way. Be aware that these example do not make any sense and are solely intended to demonstrate order of execution ;-)

Now the same scenario with a test dispatcher provided by runBlockingTest(). No need to join, and therefore no need for the job reference. This makes it a bit more readable:

A remarkable thing here is that the launched coroutine will be executed immediately, but on suspension, e.g. by yield()or delay(), execution will resume after the launch block. Luckily the DelayController implemented by the test dispatcher provides a function runCurrent() that resumes the coroutine:

Advancing Time

Sometimes we have to deal with delays, be it in code or e.g. for testing timeout. Waiting for those delays in test can be tedious:

If you run this test, it will take a bit more than 5 seconds. Now let’s do the same thing with runBlockingTest()

It takes just some milliseconds to run. The reason for that is, that the TestCoroutineDispatcher implements a virtual time, and runBlockingTest() auto-advances time, so the test will run right through. You have even access to that virtual time:

Be aware that the eager execution of a new coroutine also suspends at a delay. As an alternative to runCurrent()you can also use the advance...() function family to forward the execution of the coroutine:

That means even if this test executes in a couple of millis, the virtual time is quite reliable, so you can depend on it in your tests:

I guess that’s enough for today, the next part will be on testing timeout. As already mentioned, you will find all examples in the accompanying git repository.

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Martin Golding

--

--