如何测试ViewModel中SnapshotStateList的更新?
问题:测试ViewModel中StateList更新失败,activities始终为空
我需要测试MyViewModel里的activities状态是否通过collectActivities()方法正确收集数据并更新,但无论怎么尝试,activities始终是空的SnapshotStateList。
ViewModel代码
class MyViewModel(private val activitiesRepo: ActivitiesRepo) : ViewModel() { val activities = mutableStateListOf<Activity>() private var activitiesFlow = activitiesRepo.getAllActivitiesFlow().shareInViewModel(viewModelScope) fun initialize() { fetchActivities() collectActivities() } private fun fetchActivities() = viewModelScope.launch { activitiesRepo.fetchAllActivities() } private fun collectActivities() = viewModelScope.launch { activitiesFlow.collectLatest { newActivities -> this@MyViewModel.activities.clear() this@MyViewModel.activities.addAll(newActivities) } } }
测试代码(尝试的示例)
class MyViewModelTest { private val activitiesRepo: ActivitiesRepo = mockk(relaxed = true) private lateinit var viewModel: MyViewModel @Before fun setUp() { Dispatchers.setMain(testDispatcher) viewModel = MyViewModel(activitiesRepo = activitiesRepo) } @Test fun `initialize updates activities list`() = runTest { val mockActivities = listOf(Activity(id = 1)) every { activitiesRepo.getAllActivitiesFlow() } returns flowOf(mockActivities) viewModel.initialize() advanceUntilIdle() assertThat(viewModel.activities).containsExactlyElementsIn(mockActivities) } @AfterEach fun tearDown() { Dispatchers.resetMain() } }
问题原因
- Flow初始化时机错误:ViewModel创建时就会初始化
activitiesFlow,此时测试还没设置activitiesRepo.getAllActivitiesFlow()的mock返回值,导致activitiesFlow绑定的是mock默认返回的空Flow。 shareInViewModel的启动时机:如果这个扩展函数是立即启动(started = SharingStarted.Eagerly),会在ViewModel初始化时就开始收集Flow,此时mock数据还未注入。
解决方法
方法1:延迟Flow初始化(用lazy委托)
修改ViewModel,让activitiesFlow延迟初始化,确保mock设置完成后再创建Flow:
class MyViewModel(private val activitiesRepo: ActivitiesRepo) : ViewModel() { val activities = mutableStateListOf<Activity>() private val activitiesFlow by lazy { activitiesRepo.getAllActivitiesFlow().shareInViewModel(viewModelScope) } // 其余代码不变 }
方法2:调整测试顺序
先设置mock的返回值,再创建ViewModel:
class MyViewModelTest { private val activitiesRepo: ActivitiesRepo = mockk(relaxed = true) private lateinit var viewModel: MyViewModel @Before fun setUp() { Dispatchers.setMain(testDispatcher) } @Test fun `initialize updates activities list`() = runTest { val mockActivities = listOf(Activity(id = 1)) every { activitiesRepo.getAllActivitiesFlow() } returns flowOf(mockActivities) // 先设置mock,再创建ViewModel viewModel = MyViewModel(activitiesRepo = activitiesRepo) viewModel.initialize() advanceUntilIdle() assertThat(viewModel.activities).containsExactlyElementsIn(mockActivities) } @AfterEach fun tearDown() { Dispatchers.resetMain() } }
额外注意
如果fetchAllActivities()是用来触发数据源更新(比如从网络拉取后更新本地数据库,进而让Flow发射新数据),测试里需要确保这个方法的mock行为能触发Flow的更新。如果只是测试Flow收集逻辑,直接让getAllActivitiesFlow()返回包含测试数据的Flow即可。
内容的提问来源于stack exchange,提问作者wuujcik
相关产品推荐
相关产品推荐

