runTest vs runBlocking — Simplified

Waqas Younis
2 min read3 days ago

--

picture taken by me ;)

As the name runBlocking suggests, it runs suspending code in blocking manner, means if there is a delay inside runBlocking block, it will block the thread as well.

fun main(){

runBlocking{
delay(2000)
println("Inside runBlocking")
}
println("Outside run")

}

Inside runBlocking will be printed first and Outside run will be printed later. Just like it’s all in a sequence.

Okay, let’s look at this piece of code.

fun main(){

runBlocking{

launch{
delay(2000)
println("First launch")
}
delay(1000)
println("Inside runBlocking")
}
println("Outside run")

}

Here, code after runBlocking will be executed only when runBlocking is finished.

So the order of output would be:

Inside runBlocking
First launch
Outside run

runBlocking is not suitable for production code because you don’t want to block the underlying thread.
But it can be used for test cases because we don’t want the code to run in parallel and yield results at different points in time, which would make the testing hard, we simplify it by running in a sequence so proper assertions can be made.

Let’s move onto runTest

It’s just like runBlocking , it will block the underlying thread,
but it will automatically skip the delays using a special Dispatcher (explained later) and gives you more control over the coroutine.

👉 runTest is offered by Coroutine Testing Library. More here

Let’s write code to test this function:

suspend fun foo() : Boolean {
delay(5000)
//some operations
return true
}

You can write test, that will skip the delay and immediately return the results by using runTest

class TestExample{

@Test
fun fooTest() = runTest{
val result = foo()
assertk.assertThat(result).isTrue() //Assertk is cool
}

}

The above test will always run without any delay.

Now take a look at the following function

suspend fun doo(): Boolean{
return withContext(Dispatchers.Default){
delay(5000)
true
}
}

What do you think will happen if we write the code to test it, just like above?

The test function will not automatically skip the delay, because, as I mentioned above, runTest uses a special Dispatcher, and if the delay block is not in the same dispatcher, it will not be skipped, here the delay block is in Default dispatcher, which runTest has no control over.

How to fix such a situation?

Use the same dispatcher being used by runTest by passing it to the function being tested as an argument.

Here is how to get the dispatcher.

@OptIn(ExperimentalStdlibApi::class)
@Test
fun testDoo() = runTest {
val dispatcher = coroutineContext[CoroutineDispatcher]
val result = doo(dispatcher!!)
assertk.assertThat(result).isTrue()
}

Now, it will shine.

Before you leave
The clap button goes up to 50 if you press and hold it.
Cheers ✌️

--

--