Skip to content

ralfstuckert/kotlin-coroutines-test

Repository files navigation

Testing Kotlin Coroutines

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.

Building Blocks

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.

Examples

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 aTestCoroutineExceptionHandler` 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.

Utitlity Functions

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 the Main dispatcher and resets it after the test. The dispatcher can be resolved as a parameter in the test.

Used Libraries

  • 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages