I'm having an issue in my KMM project testing a ktor client request that is launched async within a new scope. For testing purposes I pass in Dispatchers.Unconfined
as the context of the new scope (In actual production code I'm using newSingleThreadContext()
).
I've created an extremely simplified version of the hanging ktor request below:
@ExperimentalCoroutinesApi
@Test
fun testExample(): Unit {
val scope = CoroutineScope(Dispatchers.Unconfined)
scope.launch {
val client = HttpClient { BrowserUserAgent() }
// This line hangs
val response : HttpResponse = client.get("https://google.com")
// Will never get here
println("Response: $response")
fail("This test should fail")
}
}
Note that if you don't call within the CoroutineScope.launch then it works fine. Then hang/ freeze only occurs when called within a CoroutineScope.launch. Again this is an extrememly simplified example, but in my actual code the reason have it setup this way is so that I can process some data in a background thread before ultimately making the ktor request - hence the CoroutineScope.launch. Also note that my code seems to work fine when running on an iOS simulator. It only hangs when running as a unit test.
Am I missing something to make this work, or is this a bug?
The solution for me ended up being to run my tests within runBlocking() scope, and to inject its coroutineContext
into my class to be used when running CoroutineScope.launch().
object RequestService {
private val requestContext = newSingleThreadContext("request")
var testContext by AtomicReference<CoroutineContext?>(null)
fun makeRequest(block : () -> Unit) {
CoroutineScope.launch(testContext ?: requestContext) {
block()
}
}
}
@Test
fun testExample() = runBlocking {
RequestService.testContext = this.coroutineContext
RequestService.makeRequest() {
println("Running Ktor coroutine test")
val client = HttpClient { BrowserUserAgent() }
// This line doesn't hang anymore when running within runBlocking coroutineContext
val response : HttpResponse = client.get("https://google.com")
println("Response: $response")
}
println("Finished running Ktor coroutine test")
}
Hopefully this helps someone else attempting to create a cross-platform testable service. Thanks Aleksei for comment helping me get there.