Mockito测试带协程的ViewModel:open方法验证失败问题
问题分析
你碰到的核心问题是协程异步执行时机和LiveData测试的特殊性导致的验证失败:
- 你的
open()方法是在Dispatchers.IO的协程里执行的,但测试代码里的verify会在协程完成前就同步执行,导致验证时方法还没被调用。 - 调试时能看到打印是因为调试过程有足够时间让协程跑完,但自动化测试是快速同步执行的,没有等待异步任务完成。
- 另外,你的测试规则只替换了Main调度器,
liveData(Dispatchers.IO)指定的IO调度器不受测试调度器控制,这也会导致协程无法被测试代码正确等待。
解决方案
我们需要调整测试配置,确保协程执行完成后再做验证,同时让测试调度器能控制所有协程:
步骤1:完善测试调度器配置
首先更新你的CoroutineTestRule,让所有调度器(包括IO)都使用测试调度器:
@ExperimentalCoroutinesApi class CoroutineTestRule( private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() ) : TestWatcher() { override fun starting(description: Description?) { super.starting(description) // 替换Main和IO调度器为测试调度器 Dispatchers.setMain(testDispatcher) Dispatchers.set(testDispatcher) } override fun finished(description: Description?) { super.finished(description) // 恢复默认调度器 Dispatchers.resetMain() Dispatchers.reset() } }
步骤2:添加LiveData测试规则
必须添加InstantTaskExecutorRule,它能让LiveData的所有操作在主线程同步执行,避免LiveData异步分发带来的验证问题:
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
步骤3:修改测试方法,等待协程完成
使用runBlockingTest包裹测试逻辑,并调用advanceUntilIdle()等待所有挂起的协程执行完毕:
@ExperimentalCoroutinesApi class ViewModelTest { @get:Rule val coroutineTestRule = CoroutineTestRule() @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() private lateinit var viewModel: ViewModel @Before fun setUp() { viewModel = spy(ViewModel()) } @Test fun checkTest() = coroutineTestRule.testDispatcher.runBlockingTest { // 绑定观察者,触发lazy初始化的check属性 val observer = Observer<Boolean> {} viewModel.check.observeForever(observer) // 触发shouldCheck的变化 viewModel.shouldCheck.value = true // 推进时间,等待所有协程执行完成 advanceUntilIdle() // 此时再验证方法调用 verify(viewModel, times(1)).open() // 清理观察者 viewModel.check.removeObserver(observer) } }
关键细节说明
runBlockingTest:这是测试协程专用的方法,能让协程在测试中同步执行,配合advanceUntilIdle()可以确保所有挂起任务都完成后再执行验证。- 调度器全局替换:通过
Dispatchers.set(testDispatcher),ViewModel里的Dispatchers.IO会被替换成测试调度器,这样测试代码就能完全控制协程的执行时机。 - 观察者绑定:必须先绑定观察者,才能触发
check属性的lazy初始化,否则switchMap的逻辑不会启动。
内容的提问来源于stack exchange,提问作者Rasoul Miri
相关产品推荐
相关产品推荐

