My code uses async() call wrapped with withTimeoutOrNull(), in order to cancel waiting (not the call itself) after a set amount of time, like so:
val deferredResult: Deferred<Boolean?>? = null
suspend fun fetchData(waitForDuration: Duration): Boolean? = coroutineScope {
...
val deferredResult = async {
// Some long running work
}
withTimeoutOrNull(waitForDuration) {
deferredResult.await()
}
...
}
This works as expected and returns null after timeout has elapsed, without canceling the network call.
However, when I try to cover this with a unit test, to check that in case of timeout waiting stops after X minutes and returns null, I run into a (for me) unexpected behaviour because of the coroutineScope() used by the fetchData() function.
Here is the stripped down version of the test method that fails. (Below is a version of the test method that passes, the only difference being coroutineScope)
@Test
fun `FAIL test that method stops await after timeout, without canceling call`() = runTest {
val deferredFirstCallResult = coroutineScope {
async {
delay(2.minutes)
}
}
withTimeoutOrNull(1.minutes) {
deferredFirstCallResult.await()
}
// This FAILS. Current time has already advanced to 2.minutes
assertEquals(1.minutes, currentTime.milliseconds)
// ... check that result is null etc
advanceUntilIdle()
assertEquals(2.minutes, currentTime.milliseconds)
}
Here is the one without coroutineScope():
@Test
fun `PASS test that method stops await after timeout, without canceling call`() = runTest {
val deferredFirstCallResult = async {
delay(2.minutes)
}
withTimeoutOrNull(1.minutes) {
deferredFirstCallResult.await()
}
// This is okay. Current time is 1.minutes
assertEquals(1.minutes, currentTime.milliseconds)
// ... check that result is null etc
advanceUntilIdle()
assertEquals(2.minutes, currentTime.milliseconds)
}
What am I missing here and is there a way to keep coroutineScope() in the method and check in the test that method really returns after timeout has elapsed?