使用Kotlin Flow与Jetpack Compose的ViewModel单元测试实现问题
解决方案
你遇到的问题核心是调用searchCharacter后,函数内部启动的viewModelScope协程的Flow收集逻辑还未执行完成,就尝试读取result的值,因此拿到的还是初始空列表。按以下步骤改造即可:
第一步:前置优化(可选但推荐)
当前ViewModel持有Context并直接弹Toast不符合关注点分离原则,建议把错误提示逻辑移到UI层,ViewModel仅暴露错误事件即可,这样测试时不需要mock Context,可避免很多无关报错。如果你暂时不想改业务逻辑,需要先mock Toast的调用避免测试崩溃:
如果你用Mockito,需要在测试类加静态mock:
@Before fun setup() { repository = FakeRepositoryImpl() // mock Toast的静态方法 mockStatic(Toast::class.java) `when`(Toast.makeText(any(), anyString(), anyInt())).thenReturn(mock(Toast::class.java)) viewModel = MarvelViewModel(repository) }
第二步:改造测试逻辑
调用searchCharacter后,需要让测试调度器执行完所有待执行的协程任务,再进行断言:
@OptIn(ExperimentalCoroutinesApi::class) class MarvelViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @get:Rule var mainCoroutineRule = MainCoroutineRule() private val context = mock(Context::class.java) private lateinit var viewModel: MarvelViewModel private lateinit var repository: FakeRepositoryImpl @Before fun setup() { repository = FakeRepositoryImpl() mockStatic(Toast::class.java) `when`(Toast.makeText(any(), anyString(), anyInt())).thenReturn(mock(Toast::class.java)) viewModel = MarvelViewModel(repository) } @Test fun `searchCharacter should update result state when get response success`() = runTest { // 调用业务方法 viewModel.searchCharacter("iron man", context) // 等待所有协程任务执行完成 mainCoroutineRule.scheduler.advanceUntilIdle() // 断言结果符合预期 assert(viewModel.result.value.size == 4) assert(viewModel.result.value.first().name == "Iron Man") } }
注意事项
- 如果你使用的是
kotlinx-coroutines-test1.6.0及以上版本,runBlockingTest已经废弃,替换为runTest即可,功能一致 advanceUntilIdle()会让测试协程调度器执行完所有排队的任务,确保Flow的所有发射、收集逻辑都执行完成,此时读取MutableState的value就是更新后的值
内容的提问来源于stack exchange,提问作者Henrique Tavolaro
相关产品推荐
相关产品推荐

