使用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
相关产品推荐
相关产品推荐

