This repository provides examples for that let you explore the capabilities of the kotlinx-coroutines-test module. For an introduction to the topic see my article on medium.
The kotlinx-coroutines-test module consists of four ingredients: The TestCoroutineDispatcher, the TestCoroutineExceptionHandler, a TestCoroutineScope and finally the runBlockingTest function. Let's take them step by step.
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
(we will come to that later on).
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
TestCoroutineDispatcher
implements a virtual time and gives you fine grained control on it.
This allows to write robust time based tests.
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 implementations
captures and collects all exceptions, so they can be inspected in tests,
and rethrows the first one on cleanup.
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
.
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
unitl 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.
The examples are implemented as unit tests, where each test class demonstrates certain features:
This test class shows that runBlockingTest()
actually executes launched
coroutines eagerly, which is effectively like starting it in mode UNDISPATCHED
.
The TestCoroutineDispatcher
implements a virtual time. This test class
demonstrates how to use advanceTimeBy()
and the lot to control situations
based on timing.
Due to the time control provided by the TestCoroutineDispatcher
it is
quite easy to test timeouts.
In this slight variation of the former test class, some examples are provided for testing timeout in new coroutines started via launch or async.
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.
In order to use the Test-Dispatcher the coroutines test package provides a
special function Dispatchers.setMain()
which usage is shown in this test class.
For an introduction on using the main dispatcher have a look at MainDispatcher. This class here shows usage of a (custom) JUnit 5 extension providing and maintaining a test main dispatcher for you.
As an alternative to using the Dispatchers
directly, you may use a
dispatcher provider interface which abstracts the concrete implementation,
so you may use a TestCoroutineDispatcher
as a replacement. These examples
use a neat lil library which
implements all this in an easy to use manner.
The function runBlockingTest()
counts active jobs before and after
execution of the test block, and raises an exception in case of a mismatch.
The problem is usually based in an unintential use of a non-test dispatcher.
Exceptions are not handled by default in a coroutine. The runBlockingTest()function provides a
TestCoroutineExceptionHandler` which allows you to
test exceptional situations.
The DelayController
interface implemented by the TestCoroutineDispatcher
also provides for pausing and resuming the dispatcher. These examples show
you how this switches the eager execution into a lazy one under your control.
Most of the examples provided here use [runBlockingTest] in order to
benefit of all test functionality. But you may also use every building
block on its own, like e.g. here the TestCoroutineScope
.
The coroutines package provides some utility functions used by various tests
-
TestCoroutinesExt provides some extension functions that provide access to objects in the coroutine context like e.g. the
testDispatcher
-
Asserts: Suspendable functions are colored functions. This class provides some suspendable variants of existing functions like e.g.
coAssertThrows()
-
SilentTestCoroutineExceptionHandler A variant of the original
TestCoroutineExceptionHandler
that also captures all exceptions, but do not rethrow them. -
MainDispatcherExtension This is a JUnit 5 extension that creates a
TestCoroutineDispatcher
for each test method, sets it as theMain dispatcher
and resets it after the test. The dispatcher can be resolved as a parameter in the test.
-
For mocking the MockK library is used. Besides its support for multiplatform development, it does a great job dealing with coroutines and provides lots of other features. If you do not use it yet, give it a try.
-
The ProvidingDispatchers example uses this Dispatcher Provider library. The
DispatcherProvider
is passed implicitly in the coroutine context, which lets you easily migrate from direct usage of e.g.Dispatchers.Main
without the need to change any signatures for injection.