How to test withTimeout wrapped inside of coroutineScope() with runTest, Kotlin coroutines

281 views Asked by At

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?

0

There are 0 answers