Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

421
Visualizações
How to use kotlinx.coroutines.withTimeout in kotlinx.coroutines.test.runTest?

I have a suspend function that makes a rest call to an external API that I want to timeout after 1 minute.

suspend fun makeApiCallWithTimeout(): List<ApiResponseData> =
   withTimeout(1.minutes) {
      apiCall()
   }
        

I'm trying to test it with Junit5 and kotlinx.coroutines.test 1.6.0 like so:

@Test
fun `Test api call`() = runTest {
   val responseData = "[]"
   mockWebServer.enqueue(mockResponse(body = responseData)
   val result = sut.makeApiCallWithTimeout()
   advanceUntilIdle()
   assertEquals(0, result.size)
}

Unfortunately, I'm getting errors that look like this:

Timed out waiting for 60000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 60000 ms
    at app//kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:184)
    at app//kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:154)
    at app//kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:23)
    at app//kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTask(TestCoroutineScheduler.kt:95)
    at app//kotlinx.coroutines.test.TestCoroutineScheduler.advanceUntilIdle(TestCoroutineScheduler.kt:110)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTestCoroutine(TestBuilders.kt:212)
    at app//kotlinx.coroutines.test.TestBuildersKt.runTestCoroutine(Unknown Source)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invokeSuspend(TestBuilders.kt:167)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invoke(TestBuilders.kt)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invoke(TestBuilders.kt)
    at app//kotlinx.coroutines.test.TestBuildersJvmKt$createTestResult$1.invokeSuspend(TestBuildersJvm.kt:13)
    (Coroutine boundary)

It seems that kotlinx.coroutines.test.runTest is advancing virtual time on the withTimeout without giving it any time to execute its body. See (https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/README.md#using-withtimeout-inside-runtest)

Unfortunately, the documentation doesn't provide a way to get around this.

Please advise on how to test this function using runTest.

over 4 years ago · Santiago Trujillo
2 Respostas
Responde à pergunta

0

This is because of delay-skipping.

Here you're using runTest, which brings time-control capabilities to your test. To do so, this coroutine builder provides a dispatcher with a fake time that automatically skips delays (from the real time perspective) but keeps track of the fake time internally.

From the point of view of this dispatcher, everything that doesn't have delay()s runs instantly, while things that do delay make the fake time progress.

However, this cannot be used with things that really take actual time outside of the test dispatcher, because the test will not really wait. So in essence here, withTimeout times out immediately because the actual apiCall() probably runs outside of the dispatcher (and takes real time).

You can easily reproduce this behaviour like this:

@Test
fun test() = runTest {
    withTimeout(1000) { // immediately times out
        apiCall()
    }
}

suspend fun apiCall() = withContext(Dispatchers.IO) {
    Thread.sleep(100) // not even 1s
}

There are usually 2 solutions:

  • if you want to keep using controlled time, you have to make sure you're using the test dispatcher in all the relevant code. This means that the places in your code where you use custom coroutine scopes or explicit dispatchers should allow to inject a dispatcher

  • if you don't really need controlled time, you can use runBlocking instead of runTest (on JVM) or keep using runTest but run the test on another dispatcher like Dispatchers.Default:

fun test() = runTest {
    withContext(Dispatchers.Default) {
        // test code
    }
}
over 4 years ago · Santiago Trujillo Relatório

0

An addition to Joffrey's answer

Example of injecting the scheduler if you want to keep using controlled time:

@Test
fun test() = runTest {
    val gw = MyGateway(testScheduler)
    withTimeout(1000) {
        gw.apiCall()
    }
}

class MyGateway(private val context: CoroutineContext = Dispatchers.IO) {
    companion object : Logging
    suspend fun apiCall() = withContext(context) {
        Thread.sleep(100) // not even 1s
    }
}
over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda