单元测试中如何断言Kotlin协程未完成?
问题描述
我需要测试一个挂起函数,断言它在特定条件下不会返回结果(即保持挂起状态)。我尝试了三种扩展函数方案,但都存在问题:
第一种方案
// 扩展函数 fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) { val result = async { block } advanceTimeBy(5000L) val isBlockComplete = result.isCompleted assertThat(isBlockComplete, equalTo(false)) } // 使用示例 @Test fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() = runTest { assertNotCompleted { someClass.waitForValue() } }
问题:async { block }中没有调用挂起函数(应该写block()),async内部只是返回了函数本身,导致Job立刻完成,所以result.isCompleted始终为true。此外,即使修复调用逻辑,不推进虚拟时间的话,runTest的调度器不会自动处理任务,Job状态也不会更新。
第二种方案
// 扩展函数 @OptIn(ExperimentalCoroutinesApi::class) fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) { async(block = block).run { this@assertNotCompleted.advanceTimeBy(5000L) getCompletionExceptionOrNull() } } // 使用示例 @Test(expected = IllegalStateException::class) fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() = runTest { assertNotCompleted { someClass.waitForValue() } }
问题:依赖expected注解捕获异常的设计不合理——测试中其他位置抛出的同类型异常会导致测试误通过,无法精准控制断言逻辑。
第三种方案
// 扩展函数 @OptIn(ExperimentalCoroutinesApi::class) fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) { runCatching { val result = async { block // advanceTimeBy(5000L) 测试始终通过 } // advanceTimeBy(5000L) 测试始终失败 result.getCompletionExceptionOrNull() }.also { assertThat(it.isFailure, equalTo(true)) } }
问题:同样存在未调用block()的错误,且推进时间的位置错误,导致断言结果与挂起函数的实际状态完全无关。
正确实现方案
@OptIn(ExperimentalCoroutinesApi::class) fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) { // 启动挂起函数,不等待其完成 val job = launch { block() } // 推进虚拟时间,模拟等待5秒 advanceTimeBy(5000L) // 确保调度器处理完所有待执行任务,避免状态检查滞后 runCurrent() // 断言Job仍未完成,添加自定义错误信息方便调试 assertThat(job.isCompleted) .overridingErrorMessage("挂起函数意外完成,预期会保持挂起状态") .isEqualTo(false) // 取消Job,防止测试资源泄漏 job.cancel() }
使用示例
@Test fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() = runTest { assertNotCompleted { someClass.waitForValue() } }
额外优化(可选)
如果需要确保挂起函数不会因异常结束,可以添加补充断言:
assertThat(job.isFailed) .overridingErrorMessage("挂起函数意外抛出异常") .isEqualTo(false)
内容的提问来源于stack exchange,提问作者James Olrog
相关产品推荐
相关产品推荐

