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

ViewModel单元测试疑问:为何需延迟?测试目录是否正确?

嘿,作为经常写ViewModel单元测试的人,这俩问题我太熟了!咱们一个个说清楚:

问题1:为什么需要delay(10)?怎么优化?

为什么需要延迟?

你遇到的本质是协程调度与时序问题:虽然你用了InstantTaskExecutorRule解决了LiveData的主线程同步问题,但ViewModel里调用repository.getBeerStyles()的协程逻辑,和测试代码里的verify调用之间存在调度差——测试代码的verify会在协程还没来得及触发repository调用时就执行,所以才会报“零交互”的错误。delay(10)是靠强行等待让协程完成调度,属于临时hack,不是可靠的解法。

最优实现方案

核心思路是用协程测试工具让异步逻辑同步执行,消除时序差,具体可以这么改:

  1. 替换runBlocking为runBlockingTest(旧版协程)或TestScope(新版协程),它能让挂起函数立即执行,不需要等待。
  2. 用advanceUntilIdle()代替delay,确保所有挂起的协程任务都完成后再执行验证。
  3. 移除不必要的runBlocking包裹(mock挂起函数时不需要额外包裹)。

修改后的测试代码示例:

@RunWith(JUnit4::class)
@ExperimentalCoroutinesApi
class BeerStylesViewModelTest {
    private lateinit var viewModel: BeerStylesViewModel
    private lateinit var repository: BreweryRepository
    private lateinit var beerStylesObserver: Observer<Resource<List<StylesData>>>
    private val successResource = Resource.success(mockedBeerStylesList)

    @Rule
    @JvmField
    val instantExecutorRule = InstantTaskExecutorRule()

    // 新版协程推荐用TestDispatcher替代自定义线程
    private val testDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        repository = mock()
        // 挂起函数mock不需要runBlocking包裹
        whenever(repository.getBeerStyles()).thenReturn(successResource)
        viewModel = BeerStylesViewModel(repository)
        beerStylesObserver = mock()
    }

    @Test
    fun `when load beerStyles success`() = runBlockingTest {
        viewModel.beerStyles.observeForever(beerStylesObserver)
        // 让所有挂起的协程任务执行完成
        advanceUntilIdle()
        
        verify(repository).getBeerStyles()
        verify(beerStylesObserver).onChanged(Resource.loading(null))
        verify(beerStylesObserver).onChanged(successResource)

        // 别忘了移除观察者,避免测试内存泄漏
        viewModel.beerStyles.removeObserver(beerStylesObserver)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
}

问题2:测试放在test目录而非androidTest目录是否正确?

完全正确!
你写的是纯JVM单元测试,不需要依赖Android系统环境,test目录就是专门用来存放这类测试的。你用到的InstantTaskExecutorRule、Observer等依赖,虽然属于androidx库,但它们都有对应的JVM测试artifact(比如androidx.arch.core:core-testing),可以直接在本地JVM上运行,不需要Android设备或模拟器。

而androidTest目录是用来放仪器化测试的,这类测试需要依赖Android系统(比如测试UI、ContentProvider等),你的ViewModel单元测试完全没必要放在那里。

内容的提问来源于stack exchange,提问作者Praveen P.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 16:47:32