首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在Kotlin流中运行重新配置协同线测试时的TimeoutCancellationException

在Kotlin流中运行重新配置协同线测试时的TimeoutCancellationException
EN

Stack Overflow用户
提问于 2022-04-04 01:33:51
回答 2查看 2.2K关注 0票数 1

我有一个存储库,它创建一个流,在其中我发出挂起Retrofit方法的结果。这在应用程序中是可行的,但是我想对代码进行测试。

我在测试中使用kotlinx test v1.6.0和MockWebServer v4.9.3。当我试着做测试时,我得到:

代码语言:javascript
运行
复制
Timed out waiting for 1000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 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)
    at app.cash.turbine.ChannelBasedFlowTurbine$awaitEvent$2.invokeSuspend(FlowTurbine.kt:247)
    at app.cash.turbine.ChannelBasedFlowTurbine$withTimeout$2.invokeSuspend(FlowTurbine.kt:215)
    at app.cash.turbine.ChannelBasedFlowTurbine.awaitItem(FlowTurbine.kt:252)
    at ogbe.eva.prompt.home.HomeRepositoryTest$currentTask when server responds with error emits failure$1$1.invokeSuspend(HomeRepositoryTest.kt:90)
    at app.cash.turbine.FlowTurbineKt$test$2.invokeSuspend(FlowTurbine.kt:86)
    at ogbe.eva.prompt.home.HomeRepositoryTest$currentTask when server responds with error emits failure$1.invokeSuspend(HomeRepositoryTest.kt:89)
    at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invokeSuspend(TestBuilders.kt:208)
    (Coroutine creation stacktrace)
    at app//kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:184)
    at app//kotlinx.coroutines.test.TestBuildersJvmKt$createTestResult$1.invokeSuspend(TestBuildersJvm.kt:13)
    at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest$default(TestBuilders.kt:161)
    at app//kotlinx.coroutines.test.TestBuildersKt.runTest$default(Unknown Source)
    at app//ogbe.eva.prompt.TestCoroutineRule.runTest(TestCoroutineRule.kt:26)
    at app//ogbe.eva.prompt.home.HomeRepositoryTest.currentTask when server responds with error emits failure(HomeRepositoryTest.kt:84)
    at java.base@11.0.11/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base@11.0.11/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base@11.0.11/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base@11.0.11/java.lang.reflect.Method.invoke(Method.java:566)
    at app//org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at app//org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at app//org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at app//org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at app//org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at app//org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at java.base@11.0.11/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base@11.0.11/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base@11.0.11/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base@11.0.11/java.lang.reflect.Method.invoke(Method.java:566)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 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)
    at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at app//kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
    at app//kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
    at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at app//kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at app//kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at app//kotlinx.coroutines.test.TestBuildersJvmKt.createTestResult(TestBuildersJvm.kt:12)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest(TestBuilders.kt:166)
    at app//kotlinx.coroutines.test.TestBuildersKt.runTest(Unknown Source)
    ... 50 more

我没有机会运行我的测试断言,这正是我想要做的。它只是失败了这个意外的错误。

我在流中调用了随机挂起函数,并在我的flow函数之外运行了模拟服务器。这两种方法都将在没有超时错误的情况下完成,但是当我将流、测试和重定向组合在一起时,就会显示超时错误。

存储库代码:

代码语言:javascript
运行
复制
class HomeRepository @Inject constructor(
    @IoDispatcher ioDispatcher: CoroutineDispatcher,
    private val promptService: PromptService,
) {
    val currentTask = flow {
        try {
            val response = promptService.getSchedule(1) // Suspending Retrofit method that fails the tests
            if (response.isSuccessful) {
                val schedule = response.body()
                if (schedule == null) {
                    Log.e(TAG, "Get schedule response has empty body")
                    emit(LoadState.Failure())
                } else {
                    emit(LoadState.Data(schedule.tasks.first()))
                }
            } else {
                Log.e(
                    TAG,
                    "Server responded to get schedule request with error: ${response.message()}"
                )
                emit(LoadState.Failure())
            }
        } catch (e: Exception) {
            Log.e(TAG, "Could not get schedule from server", e)
            emit(LoadState.Failure())
        }
    }
        .flowOn(ioDispatcher)

    companion object {
        private val TAG = HomeRepository::class.simpleName
    }
}

测试代码:

代码语言:javascript
运行
复制
@OptIn(ExperimentalCoroutinesApi::class)
class HomeRepositoryTest {
    @get:Rule
    val testCoroutineRule = TestCoroutineRule()

    private val mockWebServer = MockWebServer()

    @Before
    fun setUp() {
        mockWebServer.start()
    }

    @After
    fun tearDown() {
        mockWebServer.shutdown()
    }

    @Test
    fun `currentTask when server responds with error emits failure`() = testCoroutineRule.runTest {
        mockWebServer.enqueue(MockResponse().setResponseCode(500))

        val homeRepository = createRepository()

        homeRepository.currentTask.test {
            expectThat(awaitItem()).isFailure()
            awaitComplete()
        }
    }

    private fun createRepository(promptService: PromptService = createPromptService()) =
        HomeRepository(testCoroutineRule.testDispatcher, promptService)

    private fun createPromptService(): PromptService {
        val client = OkHttpClient.Builder()
            .connectTimeout(1, TimeUnit.SECONDS)
            .readTimeout(1, TimeUnit.SECONDS)
            .writeTimeout(1, TimeUnit.SECONDS)
            .build()
        return Retrofit.Builder()
            .baseUrl(mockWebServer.url("/"))
            .client(client)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
            .create(PromptService::class.java)
    }
}

规则代码:

代码语言:javascript
运行
复制
@OptIn(ExperimentalCoroutinesApi::class)
class TestCoroutineRule : TestWatcher() {
    val testDispatcher = StandardTestDispatcher()

    private val testScope = TestScope(testDispatcher)

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
    }

    fun runTest(block: suspend TestScope.() -> Unit) =
        testScope.runTest(testBody = block)
}

如何测试使用Retrofit而不出现超时错误的流?

EN

Stack Overflow用户

回答已采纳

发布于 2022-04-11 17:54:02

我遇到了同样的问题。我发现这一切都是因为kotlin-coroutines test 1.6.0,更具体地说,是因为runTest行为。

runTest允许您控制虚拟时间(如runBlockingTest),这在测试时非常方便。但是这里的问题是,您使用的是OkHttp (MockWebServer in test ),它运行在它自己的线程中,并且在测试调度程序之外使用实时。

这里的解决办法是

如果不需要控制虚拟时间,例如调用(),则使用runBlocking代替runTest,例如,通过用"withContext(Dispatchers.Default)“或”runTest“包装测试内容,在另一个调度程序上运行测试。

代码语言:javascript
运行
复制
    @Test
    fun `my unit test`() = runTest {
        withContext(Dispatchers.Default) { // can be either Dispatchers.Default or Dispatchers.IO but not Dispatchers.Main
            // enter code here
        }
    }

另一种解决方案是通过在测试类中创建一个属性,为主调度程序提供一个不同的线程上下文,如下所示:

代码语言:javascript
运行
复制
    @OptIn(DelicateCoroutinesApi::class)
    private val mainThreadSurrogate = newSingleThreadContext("UI thread")

    @Before
    fun setUp() {
        Dispatchers.setMain(mainThreadSurrogate)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        mainThreadSurrogate.close()
    }


    @Test
    fun `my unit test`() = runTest {
        withContext(Dispatchers.Main) { // this is not mandatory here as the default dispatcher is Main
            // enter code here
        }
    }

我对协同测试和协同测试非常陌生,所以我可能误解了一些细节,但我希望它能有所帮助。

文档也可以方便使用:https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/README.md

票数 1
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71731166

复制
相关文章

相似问题

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