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

如何测试ViewModel初始化阶段发射的StateFlow与SharedFlow值

解决方案

核心思路

通过控制测试协程调度器的执行时机,阻止ViewModel初始化时的协程立即执行,等你完成对两个Flow的订阅后再恢复调度,就能捕获到初始化逻辑中发射的所有事件。

前置依赖

确保项目引入了kotlinx-coroutines-test测试依赖,版本1.6.0及以上即可。

测试代码示例

@OptIn(ExperimentalCoroutinesApi::class)
class MyViewModelTest {
    // 测试网络请求成功场景
    @Test
    fun `init network call success emits expected state`() = runTest {
        // 模拟网络请求成功,返回预期的新状态
        val expectedNewState = LoadSuccessState(data = "test")
        mockNetworkSuccess(expectedNewState)
        
        // 暂停调度器,阻止初始化协程立即执行
        testScheduler.pauseDispatcher()
        // 创建ViewModel,此时performNetworkCall不会执行
        val viewModel = MyViewModel<ViewState>(initialState = LoadingState)
        
        viewModel.state.test {
            viewModel.errorFlow.test {
                // 恢复调度器,开始执行初始化逻辑
                testScheduler.resumeDispatcher()
                
                // 验证StateFlow先发射初始状态,再发射成功后的新状态
                assertEquals(LoadingState, awaitItem())
                assertEquals(expectedNewState, awaitItem())
                // 验证errorFlow没有发射任何事件
                expectNoEvents()
            }
        }
    }

    // 测试网络请求失败场景
    @Test
    fun `init network call failure emits error event`() = runTest {
        // 模拟网络请求失败
        mockNetworkFailure()
        
        testScheduler.pauseDispatcher()
        val viewModel = MyViewModel<ViewState>(initialState = LoadingState)
        
        viewModel.state.test {
            viewModel.errorFlow.test {
                testScheduler.resumeDispatcher()
                
                // 验证StateFlow仅发射初始状态,无后续更新
                assertEquals(LoadingState, awaitItem())
                expectNoEvents()
                // 验证errorFlow发射预期错误信息
                assertEquals("network failure", awaitItem())
            }
        }
    }

    // 模拟网络请求成功,此处以MockK为例,Mockito写法逻辑一致
    private fun mockNetworkSuccess(returnState: ViewState) {
        coEvery { 
            Network.makeCall(eq("/someEndpoint"), any(), any()) 
        } answers {
            secondArg<(ViewState) -> Unit>().invoke(returnState)
        }
    }

    // 模拟网络请求失败
    private fun mockNetworkFailure() {
        coEvery { 
            Network.makeCall(eq("/someEndpoint"), any(), any()) 
        } answers {
            thirdArg<() -> Unit>().invoke()
        }
    }
}

补充说明

  • 如果你只需要验证state的最终值,不需要校验中间状态,不需要暂停调度器,直接在ViewModel创建完成后调用viewModel.state.first()获取最新值断言即可,因为StateFlow本身会持有最新状态。
  • 如果你业务逻辑允许新订阅者获取之前的错误事件,也可以把errorFlow的replay属性设置为1:val errorFlow: SharedFlow<String> = MutableSharedFlow(replay = 1),这种情况下不需要暂停调度器,初始化完成后订阅也能拿到之前发射的错误值,注意该修改会改变生产环境的行为,需要确认符合产品需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 00:09:03