Android Kotlin中runTest测试ViewModel时验证失败求助
JUnit4 + Kotlin 1.8.10中runTest测试ViewModel协程逻辑失败的问题
问题场景
- 测试
LoadingViewModel的checkXyz方法时,遇到runTest验证失败的问题 checkXyz内部通过viewModelScope.launch(后续指定了ioDispatcher)执行delay(1000L)后将xyz设为true- 测试用
spyk创建ViewModel实例,在runTest中调用checkXyz后,验证subject.xyz = true失败;移除delay测试正常,指定ioDispatcher后问题依旧
错误信息
Verification failed: call 1 of 1: LoadingViewModel(#3).setXyz(eq(true))) was not called.
Calls to same mock:
- LoadingViewModel(#3).checkXyz()
- LoadingViewModel(#3).getTag(androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY)
- LoadingViewModel(#3).setTagIfAbsent(androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY, androidx.lifecycle.CloseableCoroutineScope@2d5580f6)
解决思路与方案
问题核心是viewModelScope的真实调度器与runTest的测试调度器未对齐,导致测试代码在协程完成delay逻辑前就执行了验证。
方案1:给ViewModel注入可替换的调度器
修改ViewModel构造,允许传入自定义调度器:
class LoadingViewModel( private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : ViewModel() { var xyz = false fun checkXyz() { viewModelScope.launch(dispatcher) { delay(1000L) xyz = true } } }
测试时使用测试调度器,并推进到所有任务完成:
@Test fun testCheckXyz() = runTest { val testDispatcher = UnconfinedTestDispatcher(testScheduler) val viewModel = spyk(LoadingViewModel(testDispatcher)) viewModel.checkXyz() advanceUntilIdle() // 让测试调度器执行完所有挂起任务 verify { viewModel.xyz = true } }
方案2:手动替换viewModelScope的调度器
若无法修改ViewModel构造,可在测试中替换viewModelScope的调度器:
@Test fun testCheckXyz() = runTest { val viewModel = spyk(LoadingViewModel()) // 替换viewModelScope为绑定测试调度器的作用域 val testScope = CloseableCoroutineScope(UnconfinedTestDispatcher(testScheduler)) viewModel.setTagIfAbsent(ViewModelCoroutineScope.JOB_KEY, testScope) viewModel.checkXyz() advanceUntilIdle() verify { viewModel.xyz = true } }
关键注意点
- 必须调用
advanceUntilIdle()或advanceTimeBy(1000L),确保测试调度器执行完delay后的逻辑,否则验证会在协程挂起时提前执行 - 测试中避免直接使用真实调度器(如
Dispatchers.IO),这类调度器不会被runTest自动接管,需手动替换为测试调度器
内容的提问来源于stack exchange,提问作者Compose Learner
相关产品推荐
相关产品推荐

