Testing Coroutines and Asynchronous Calls

The getAirportStatus() function is making synchronous calls, one at a time, to the getAirportData() function. If we receive a large number of airport codes, then making blocking calls, one by one, won’t be efficient. If we make the calls to getAirportData() asynchronous, using coroutines, then we can get a better throughput. Of course, we’ll have to test first and then write the code for asynchronous execution.

Making asynchronous calls

For getAirportStatus() to make asynchronous calls, we have to do three things. First, mark the function with the suspend keyword. Then execute the body of the function in the context of a Dispatchers.IO thread pool. Finally, embed the calls to getAirportData() within async calls and await for the results by applying the techniques we saw in the previous chapter (Asynchronous Programming). We’ll write a test for each of these three steps and then implement the code.

The instant we mark the getAirportStatus() function with suspend, the previous test will fail compilation with the error “Suspension functions can be called only within the coroutine body.” To address this, let’s go back to the previous test and embed the call to getAirportStatus() within a call to runBlocking(), like so:

// AirportStatusTest.kt
runBlocking { getAirportStatus(input) shouldBe result }

Remember to import the necessary coroutines library for this code to compile:

// AirportStatusTest.kt
import kotlinx.coroutines.*

Now we can mark the function getAirportStatus() with the suspend keyword.

// AirportStatus.kt
suspend fun getAirportStatus(airportCodes: List<String>): List<Airport> = 
  Airport.sort(
    airportCodes.map { code -> Airport.getAirportData(code) })

Before we can make an asynchronous call, we have to decide which thread pool to run the request in. Since getAirportData() will be making a call to a remote web service, it’s appropriate to run the request in an IO thread pool. For this reason, we’ll embed the body of getAirportStatus() within a call to withContext() so the code will run in the coroutine context we provide to that function. In short, our test needs to verify that getAirportStatus() makes a call to withContext(). How in the world can we do that?

We can write an interaction test by mocking the withContext() function. But that requires a bit of digging in. The withContext() function is a top-level function defined in the kotlinx.coroutines package. Sadly, we can’t simply tell the Mockk library to mock the withContext function, as it won’t know which function we’re referring to. Even though Kotlin has top-level functions, at the bytecode level they don’t reside directly in the package, but within a class. We have to track down which class, in the bytecode, the top-level function withContext() has been compiled into and ask Mockk to mock that class—yikes, that’s going to take some effort.

Finding the class name

Visit the documentation for the withContext() function, and click the source link that is next to the return type T of the function. You’ll notice that the file that has this function is Builders.common.kt, and in that file withContext() is defined as a top-level function. Now we have to find what class this code is compiled into. For this, you’ll need the super-investigative skills of Sherlock Holmes and the jar tool that is part of the JDK. Download the jar file kotlinx-coroutines-core-1.2.2.jar by clicking the jar link in the Maven repository page, and use the jar -tf command, on the command line, to find the class name. If you’re on a Unix-like system, use this command:

jar -tf kotlinx-coroutines-core-1.2.2.jar | grep Builders | grep common

If you’re on Windows, then use the following command:

jar -tf kotlinx-coroutines-core-1.2.2.jar | Find "Builders" | Find "common"

The fruit of the that effort is the output:

kotlinx/coroutines/BuildersKt__Builders_commonKt.class

This tells us the code in the file Builders.common.kt is compiled into the class named BuildersKt__Builders_commonKt. That’s the class we should mock to replace the withContext() method with the fake implementation in test.

Let’s write the test for verifying that getAirportStatus() is calling withContext() with the appropriate arguments.

Get hands-on with 1400+ tech skills courses.