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

使用StandardTestDispatcher时,ViewModel中launch未等待就执行的原因

问题

我有一个在init块中使用viewModelScope.launch{}的ViewModel。在测试中我设置了Dispatchers.setMain(StandardTestDispatcher()),但launch{}在调用advanceUntilIdle()前就执行了,这是为什么?它难道不该等待吗?

ViewModel代码:

class TestViewModel : ViewModel() {

    val state = MutableStateFlow("A")

    init {
        viewModelScope.launch {
            state.update { "B" }
        }
    }
}

测试代码:

class TestViewModelTest {

    private lateinit var viewModel: TestViewModel

    @Before
    fun setup() {
        Dispatchers.setMain(StandardTestDispatcher())
        viewModel = TestViewModel()
    }

    @Test
    fun test() = runTest {
        assert(viewModel.state.value == "A") // Fails, value is "B"
        advanceUntilIdle()
        assert(viewModel.state.value == "B")
    }

}
原因分析

核心问题是调度器实例不统一:

  • @Before方法中创建的StandardTestDispatcher是独立实例,设置为Main调度器后,ViewModel初始化时的viewModelScope.launch会使用这个调度器。
  • runTest函数默认会生成一个全新的StandardTestDispatcher作为测试作用域的调度器,和setup中设置的不是同一个实例。
  • setup中启动的协程不受runTest作用域控制,那个独立的调度器不会等待advanceUntilIdle(),会直接执行协程代码,导致测试开始前state.value就变成了"B"。
解决方案

有两种常用修复方式:

方式一:在runTest内部设置Main调度器

将ViewModel的创建移到runTest块内,使用测试作用域提供的调度器实例设置为Main:

class TestViewModelTest {

    @Test
    fun test() = runTest {
        Dispatchers.setMain(this.dispatcher) // 复用runTest作用域的调度器
        val viewModel = TestViewModel()
        
        assert(viewModel.state.value == "A") // 断言成功
        advanceUntilIdle()
        assert(viewModel.state.value == "B")
    }
}

方式二:共享同一个调度器实例

在setup中创建共享的StandardTestDispatcher,并传入runTest,确保所有协程受同一调度器控制:

class TestViewModelTest {

    private lateinit var viewModel: TestViewModel
    private val testDispatcher = StandardTestDispatcher()

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        viewModel = TestViewModel()
    }

    @Test
    fun test() = runTest(testDispatcher) { // 传入共享调度器
        assert(viewModel.state.value == "A") // 断言成功
        advanceUntilIdle()
        assert(viewModel.state.value == "B")
    }
}

两种方式都能让viewModelScope启动的协程受runTest作用域控制,仅在调用advanceUntilIdle()时执行。

内容的提问来源于stack exchange,提问作者Tgo1014

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 10:55:53