You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程测试遇UncompletedCoroutinesError问题排查求助

问题:Kotlin协程测试触发UncompletedCoroutinesError

错误详情

报错信息:

kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 1m, the test coroutine is not completing

该错误出现在KotlinExampleTests类的测试用例Test whether the Example widget icons are displayed in zero state中。

测试代码片段

@Test
fun `Test whether the Example widget icons are displayed in zero state`() = runTestSafe {
    val dispatcher = StandardTestDispatcher(testScheduler)
    val viewModel = createViewModel(dispatcher, this)

    viewModel.responseFlow.value = mockResponseFlow()

    composeTestRule.setContent {
        ExampleWidget(
            Modifier,
            mockZeroState(),
            { _, _-> },
            { _, _ -> },
            { _, _ -> },
            { _ -> },
            { _-> }
        )
    }

    composeTestRule.onNodeWithTag("Tag1").assertExists()
    composeTestRule.onNodeWithTag("Tag2").assertExists()
    composeTestRule.onNodeWithTag("Tag3").assertExists()
    assert(0 == viewModel.mockResponseFlow.value?.icons?.size)
}

已尝试操作

  • 增大runTest超时时间至1分钟
  • 验证测试中启动的协程在测试结束时已被正确取消

现象

  • 单独运行该测试套件时通过
  • 运行全部测试套件时失败

原因分析
  1. 测试间状态泄漏:全量测试运行时,前序测试的协程、Dispatcher状态或Compose组件状态未被完全清理,导致当前测试的协程被阻塞。
  2. TestDispatcher未正确隔离:StandardTestDispatcher如果和其他测试共享testScheduler,可能导致调度任务交叉干扰,使得当前测试的协程无法完成。
  3. Compose测试的协程未同步:setContent中可能存在未被composeTestRule管控的协程,或者UI断言后未等待Compose帧完成,导致协程残留。
  4. ViewModel协程作用域问题:createViewModel传入的作用域如果未正确绑定测试的runTest作用域,可能导致ViewModel中的协程脱离测试管控,无法被及时取消。

排查与修复方案

排查步骤

  1. 检查createViewModel方法的实现,确认传入的this(测试作用域)是否被正确用作ViewModel的CoroutineScope,ViewModel是否在测试结束时被正确销毁。
  2. 查看前序测试是否有未清理的协程或Dispatcher状态,比如是否共享了TestDispatcher实例。
  3. 在测试的关键节点(如断言后)添加composeTestRule.waitForIdle(),观察是否能解决问题,验证是否是Compose协程未同步导致。

修复方案

  1. 隔离TestDispatcher:每个测试用例创建独立的StandardTestDispatcher,不共享testScheduler,避免测试间的调度干扰:
@Test
fun `Test whether the Example widget icons are displayed in zero state`() = runTest {
    val testDispatcher = StandardTestDispatcher()
    val viewModel = createViewModel(testDispatcher, this)
    // ... 其余代码
}
  1. 强制同步Compose状态:在UI断言后添加waitForIdle(),确保Compose的所有协程任务完成:
composeTestRule.onNodeWithTag("Tag3").assertExists()
composeTestRule.waitForIdle() // 等待Compose完成所有UI渲染任务
assert(0 == viewModel.mockResponseFlow.value?.icons?.size)
  1. 确保ViewModel作用域绑定测试Scope:检查createViewModel的实现,确保ViewModel的CoroutineScope使用测试传入的作用域,或者在测试结束时手动取消ViewModel的作用域:
// 示例createViewModel实现
fun createViewModel(dispatcher: Dispatcher, scope: CoroutineScope): ExampleViewModel {
    return ExampleViewModel(scope + dispatcher)
}
  1. 添加测试后清理逻辑:在@AfterEach方法中清理Compose状态和ViewModel资源,避免状态泄漏:
@AfterEach
fun tearDown() {
    composeTestRule.clearContent()
    // 如果有需要,手动取消ViewModel的协程作用域
}
  1. 替换runTestSafe为标准runTest:如果runTestSafe是自定义封装,可能存在协程管控的问题,改用官方kotlinx.coroutines.test.runTest,确保协程的自动取消和管控。

内容的提问来源于stack exchange,提问作者Kotiswar Kaithepalli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:52:44