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

使用runTest测试SharedFlow超时60秒后报UncompletedCoroutinesError

问题原因

你将TestScope作为externalScope传入ExampleClass,而shareIn会在该Scope下启动一个长期运行的协程来维护SharedFlow的共享状态。runTest会在测试结束时强制检查该Scope下所有协程是否已完成或取消,这个持续运行的共享协程未被终止,因此触发UncompletedCoroutinesError。

解决方案

方案1:使用独立的外部CoroutineScope(不修改原类)

创建一个与测试Scope分离的独立Scope,用于shareIn,测试结束后手动取消该Scope:

class ExampleClassTest {
    private val mockApi = mockk<Api> { coEvery { getData() } returns "hello" }
    private val testScope = TestScope()
    // 独立的外部Scope,与测试Scope解耦
    private val externalScope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)

    @AfterTest
    fun tearDown() {
        // 测试结束后取消外部Scope,终止共享协程
        externalScope.cancel()
    }

    @Test
    fun thisTestPasses() = testScope.runTest {
        val exampleClass = ExampleClass(mockApi, externalScope)
        assertEquals("hello", exampleClass.performLogin())
    }
}

方案2:修改原类的SharingStarted策略(若允许调整业务逻辑)

将shareIn的启动策略改为WhileSubscribed,这样当没有订阅者时,共享协程会自动停止:

// 修改ExampleClass中的dataFlow定义
val dataFlow = _dataFlow.shareIn(externalScope, SharingStarted.WhileSubscribed(5000))

然后在测试中临时订阅流,测试完成后取消订阅:

@Test
fun thisTestPasses() = testScope.runTest {
    val exampleClass = ExampleClass(mockApi, testScope)
    // 启动临时订阅
    val subscribeJob = launch {
        exampleClass.dataFlow.collect()
    }
    assertEquals("hello", exampleClass.performLogin())
    // 取消订阅,触发共享协程停止
    subscribeJob.cancel()
}

方案3:在测试中强制取消TestScope的子协程(不推荐,可能隐藏问题)

在测试末尾手动取消TestScope,强制终止所有子协程:

@Test
fun thisTestPasses() = testScope.runTest {
    val exampleClass = ExampleClass(mockApi, testScope)
    assertEquals("hello", exampleClass.performLogin())
    // 手动取消TestScope,终止共享协程
    testScope.cancel()
}

此方法不推荐,因为可能掩盖其他未正确管理的协程问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 00:10:20