添加yield()导致协程单元测试失败?如何测试可取消协作函数?
协程yield()导致单元测试失败的原因及解决方案
一、测试失败的核心原因
添加yield()后测试失败,本质是runBlockingTest与TestCoroutineDispatcher的虚拟时间调度机制导致的:
runBlockingTest会同步执行协程内的立即代码,但yield()会触发协程挂起,将后续的titleDao.insertTitle(...)任务排入TestCoroutineDispatcher的任务队列。- 默认情况下,
runBlockingTest不会自动处理队列中挂起的任务,导致断言执行时,插入操作还未完成,最终断言失败。
二、带yield()的可取消协作函数的测试方案
1. 手动推进虚拟时间(适配老版测试API)
在调用目标函数后,调用advanceUntilIdle(),让TestCoroutineDispatcher执行所有排队的挂起任务:
@Test fun whenRefreshTitleSuccess_insertsRows() = runBlockingTest() { val titleDao = TitleDaoFake("title") val subject = TitleRepository( network = MainNetworkFake("OK"), titleDao = titleDao, ioDispatcher = TestCoroutineDispatcher(), ) subject.refreshTitle() advanceUntilIdle() // 触发所有挂起任务执行 Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK") }
2. 使用新版测试调度器(推荐)
如果使用Kotlin Coroutines 1.6.0+的新测试API,改用StandardTestDispatcher配合runTest,它会自动处理挂起任务的调度,无需手动推进:
@Test fun whenRefreshTitleSuccess_insertsRows() = runTest { val titleDao = TitleDaoFake("title") val subject = TitleRepository( network = MainNetworkFake("OK"), titleDao = titleDao, ioDispatcher = StandardTestDispatcher(testScheduler), ) subject.refreshTitle() Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK") }
3. 测试协作取消逻辑
要验证yield()带来的可取消特性,可以在协程执行到yield()前取消任务,验证插入操作是否被终止:
@Test fun whenRefreshTitleCancelled_doesNotInsertRows() = runBlockingTest() { val titleDao = TitleDaoFake("title") val subject = TitleRepository( network = MainNetworkFake("OK"), titleDao = titleDao, ioDispatcher = TestCoroutineDispatcher(), ) val job = launch { subject.refreshTitle() } job.cancel() // 在yield()挂起点前取消协程 advanceUntilIdle() Truth.assertThat(titleDao.nextInsertedOrNull()).isNull() }
内容的提问来源于stack exchange,提问作者Mohammadreza Khahani
相关产品推荐
相关产品推荐

