You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android MVVM单元测试中ViewModel回调未执行问题排查

解决Android ViewModel单元测试中Dispatchers.Main回调不执行的问题

问题核心

测试环境没有Android主线程,直接使用Dispatchers.Main会导致协程调度阻塞,即便添加了InstantTaskExecutorRule或UnconfinedTestDispatcher,也未正确替换协程的主线程调度逻辑,最终导致withContext(Dispatchers.Main)内的回调无法执行。

解决方案步骤

  1. 添加协程测试依赖
    确保Module级build.gradle中引入与项目协程版本匹配的测试库:

    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3"
    
  2. 用StandardTestDispatcher全局替换调度器
    放弃UnconfinedTestDispatcher(易引发时序问题),改用StandardTestDispatcher统一替换所有协程调度器:

    private val testDispatcher = StandardTestDispatcher()
    
    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        // 初始化ViewModel、Mock Repository等测试依赖
    }
    
    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
    
  3. 配合InstantTaskExecutorRule处理LiveData
    若ViewModel中用到LiveData,必须添加该规则保证测试中LiveData正常工作:

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()
    
  4. 触发协程后推进调度器执行
    调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 13:27:25