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

如何测试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()
    }
}

问题原因

  1. Flow初始化时机错误:ViewModel创建时就会初始化activitiesFlow,此时测试还没设置activitiesRepo.getAllActivitiesFlow()的mock返回值,导致activitiesFlow绑定的是mock默认返回的空Flow。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:31:14