Android MVVM单元测试中ViewModel回调未执行问题排查
解决Android ViewModel单元测试中Dispatchers.Main回调不执行的问题
问题核心
测试环境没有Android主线程,直接使用Dispatchers.Main会导致协程调度阻塞,即便添加了InstantTaskExecutorRule或UnconfinedTestDispatcher,也未正确替换协程的主线程调度逻辑,最终导致withContext(Dispatchers.Main)内的回调无法执行。
解决方案步骤
添加协程测试依赖
确保Module级build.gradle中引入与项目协程版本匹配的测试库:testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3"用StandardTestDispatcher全局替换调度器
放弃UnconfinedTestDispatcher(易引发时序问题),改用StandardTestDispatcher统一替换所有协程调度器:private val testDispatcher = StandardTestDispatcher() @Before fun setup() { Dispatchers.setMain(testDispatcher) // 初始化ViewModel、Mock Repository等测试依赖 } @After fun tearDown() { Dispatchers.resetMain() }配合InstantTaskExecutorRule处理LiveData
若ViewModel中用到LiveData,必须添加该规则保证测试中LiveData正常工作:@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()触发协程后推进调度器执行
调用ViewModel的logIn方法后,必须调用testDispatcher.advanceUntilIdle(),让所有挂起协程执行完毕,包括withContext(Dispatchers.Main)内的代码:@Test fun `login success should trigger callback`() { // Mock Repository返回成功响应 val mockSuccessResponse = LoginResponse(success = true) `when`(mockRepository.logIn(anyString(), anyString())).thenReturn(mockSuccessResponse) // 捕获回调参数 val callbackCaptor = argumentCaptor<(LoginResponse) -> Unit>() viewModel.logIn("test@test.com", "123456", callbackCaptor.capture()) // 推进调度器,执行所有待处理协程任务 testDispatcher.advanceUntilIdle() // 验证回调是否被正确调用 verify(callbackCaptor.value).invoke(mockSuccessResponse) }
优化建议
不要在ViewModel中硬编码Dispatchers.IO或Dispatchers.Main,通过构造函数传入调度器,进一步降低测试耦合:
class AppViewModel( private val repository: AuthRepository, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main ) : ViewModel() { fun logIn(username: String, password: String, callback: (LoginResponse) -> Unit) { viewModelScope.launch(ioDispatcher) { val response = repository.logIn(username, password) withContext(mainDispatcher) { callback.invoke(response) } } } }
测试时直接传入testDispatcher作为两个调度器参数,简化测试配置。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

