我有一个挂起函数,它对外部API进行rest调用,我想在1分钟后超时。
suspend fun makeApiCallWithTimeout(): List<ApiResponseData> =
withTimeout(1.minutes) {
apiCall()
}我试图用Junit5和kotlinx.coroutines.test 1.6.0来测试它,如下所示:
@Test
fun `Test api call`() = runTest {
val responseData = "[]"
mockWebServer.enqueue(mockResponse(body = responseData)
val result = sut.makeApiCallWithTimeout()
advanceUntilIdle()
assertEquals(0, result.size)
}不幸的是,我遇到了这样的错误:
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测试此功能。
发布于 2022-01-11 00:16:19
这是因为延迟跳过。
这里使用的是runTest,它为您的测试带来了时间控制功能。要做到这一点,这个协同机制构建器提供了一个假时间,它自动跳过延迟(从实时角度来看),但在内部跟踪假时间。
从这个分配器的角度来看,所有没有delay()的东西都会立即运行,而延迟的东西会使假的时间进步。
但是,这不能用于在测试调度程序之外实际花费时间的事情,因为测试不会真正等待。因此,在这里本质上,withTimeout会立即超时,因为实际的apiCall()可能在调度程序之外运行(并且需要实时运行)。
您可以很容易地复制这样的行为:
@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,但是可以在另一个调度程序(如)上运行测试
fun test() = runTest {
withContext(Dispatchers.Default) {
// test code
}
}发布于 2022-04-12 05:28:08
乔佛里回答的补充
如果要继续使用受控时间,则注入调度程序的示例:
@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
}
}https://stackoverflow.com/questions/70658926
复制相似问题