首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在kotlinx.coroutines.withTimeout中使用kotlinx.coroutines.test.runTest?

如何在kotlinx.coroutines.withTimeout中使用kotlinx.coroutines.test.runTest?
EN

Stack Overflow用户
提问于 2022-01-10 21:10:56
回答 2查看 1.6K关注 0票数 4

我有一个挂起函数,它对外部API进行rest调用,我想在1分钟后超时。

代码语言:javascript
复制
suspend fun makeApiCallWithTimeout(): List<ApiResponseData> =
   withTimeout(1.minutes) {
      apiCall()
   }

我试图用Junit5和kotlinx.coroutines.test 1.6.0来测试它,如下所示:

代码语言:javascript
复制
@Test
fun `Test api call`() = runTest {
   val responseData = "[]"
   mockWebServer.enqueue(mockResponse(body = responseData)
   val result = sut.makeApiCallWithTimeout()
   advanceUntilIdle()
   assertEquals(0, result.size)
}

不幸的是,我遇到了这样的错误:

代码语言:javascript
复制
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)

kotlinx.coroutines.test.runTest似乎在推进withTimeout上的虚拟时间,而没有给它任何时间来执行它的身体。见(https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/README.md#using-withtimeout-inside-runtest)

不幸的是,文档并没有提供一种解决这个问题的方法。

请建议如何使用runTest测试此功能。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-01-11 00:16:19

这是因为延迟跳过。

这里使用的是runTest,它为您的测试带来了时间控制功能。要做到这一点,这个协同机制构建器提供了一个假时间,它自动跳过延迟(从实时角度来看),但在内部跟踪假时间。

从这个分配器的角度来看,所有没有delay()的东西都会立即运行,而延迟的东西会使假的时间进步。

但是,这不能用于在测试调度程序之外实际花费时间的事情,因为测试不会真正等待。因此,在这里本质上,withTimeout会立即超时,因为实际的apiCall()可能在调度程序之外运行(并且需要实时运行)。

您可以很容易地复制这样的行为:

代码语言:javascript
复制
@Test
fun test() = runTest {
    withTimeout(1000) { // immediately times out
        apiCall()
    }
}

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

通常有两种解决方案:

如果要继续使用受控时间,则必须确保在所有相关代码中使用测试调度程序。这意味着代码中使用自定义协同线作用域或显式调度器的位置应该允许注入dispatcher。

如果您真的不需要控制时间,您可以使用Dispatchers.Default而不是runTest (在JVM上),或者继续使用runTest,但是可以在另一个调度程序(如)上运行测试

代码语言:javascript
复制
fun test() = runTest {
    withContext(Dispatchers.Default) {
        // test code
    }
}
票数 4
EN

Stack Overflow用户

发布于 2022-04-12 05:28:08

乔佛里回答的补充

如果要继续使用受控时间,则注入调度程序的示例:

代码语言:javascript
复制
@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
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70658926

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档