How To Test Kotlin Coroutines?. Make your apps bug free | by Farhan Tanvir | Mar, 2022

Make your apps bug free

Photo by ThisisEngineering RAEng on Unsplash

Asynchronous or non-blocking is an important aspect of the development process. From Kotlin 1.3, Coroutines was introduced for this purpose. It has much functionality like running many coroutines from a single thread, can be suspended from one thread and resuming from another thread, etc.

It is also considered a lightweight thread. Nowadays it becomes one of the fundamentals of android development. Today we will learn about how to write a Unit test for it.

Add the latest kotlinx-coroutines-test version in the app build.gradle . Currently, it is 1.6.0 .

testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0'

Simply put, a suspend function is one that can be suspended and resumed at a later time. They can start a long-running operation and then wait for it to finish without blocking the main thread.

Let’s test a Suspend function.

runTest

I believe the code is self-explanatory. Here we are testing Car class. We just need to run the test inside runTest which will run the test in testScope .

First, we will know what are dispatchers.

In simple words, Dispatchers help to decide in which thread the task should be done. There are four types of dispatchers.

  1. Dispatchers.Default: It uses a shared pool of background threads. This is a good option for compute-intensive coroutines that need a lot of CPU power.
  2. Dispatchers.Main: As the name suggested it used the main thread. It is used for small lightweight task
  3. Dispatchers.IO: It uses a shared pool of on-demand background threads. This is used mostly for networking, local DB, File handling, etc.
  4. Dispatchers.Unconfined: It will run on the current thread. The Unconfined the dispatcher should not normally be used in code.

Let’s test a Dispatchers Coroutine.

It also should be OK. Is not it?

If we run this, we will find the test failed.

It will show an AssertionError. Because our test will execute on a separate thread than the production code.

According to the official documentation:

Don’t hardcode Dispatchers when creating new coroutines or calling withContext.

In a simple word, use a dependency injection pattern to injecting Dispatchers . So we should modify the car class.

class Car(private val defaultDispatcher: CoroutineDispatcher) {
var fuel = 0
fun addFuel() {
CoroutineScope(
defaultDispatcher).launch {
fuel = 50
}
}
}

To test this we also need a TestDispatcher . There are two types of TestDispatcher .

  1. StandardTestDispatcher: No tasks are automatically executed by this dispatcher. Instead of immediately running coroutines it launched with a pending state. In other words, we have to run the coroutines manually but we will have full control.
  2. UnconfinedTestDispatcher: Tasks are automatically executed by this dispatcher. There is no guarantee in the order and you will have no control.

But according to me, in most cases, UnconfinedTestDispatcher will be enough. Though it is debatable. Also, according to the official documentation:

Using this TestDispatcher can greatly simplify writing tests where it’s not important which thread is used when and in which order the queued coroutines are executed. Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without going through a dispatch; this can be helpful for testing Channel and StateFlow usages.

In this example, we will use UnconfinedTestDispatcher. Now, we have to modify the CarTest class.

class CarTest {
private val testDispatcher = UnconfinedTestDispatcher()
private val car = Car(testDispatcher)
@Test
fun addFuel() = runBlocking{
car.addFuel()
Assert.assertEquals(50,car.fuel)
}
}

If we run the test now it will be passed without any issue.

Hoorrraaayyy! The test passed.

Leave a Comment