使用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
相关产品推荐
相关产品推荐

