夹具(Fixture)基础概念
在 ViewModel 测试中,夹具(Fixture)是指一组预先准备好的状态、依赖或环境配置,用于在可控的上下文中执行被测代码。它解决的核心问题是:如何在不依赖真实外部系统的情况下,为 ViewModel 提供确定性的输入状态?夹具通过三个关键维度——状态容器、生命周期管理和与 ViewModel 的注入关系——实现这一目标。
1. 夹具定义
一个 ViewModel 测试夹具通常是一个类或对象,负责:
- 初始化 ViewModel 所需的依赖(如 Repository、状态流)。
- 预设 ViewModel 的初始状态(例如加载中、空数据、错误等)。
- 暴露 用于断言的状态观察点(StateFlow / LiveData)。
- 清理 测试结束后释放资源,避免跨测试污染。
核心原则:每个测试用例都应拥有独立的夹具实例,以保证测试隔离性。
2. 状态容器
ViewModel 的状态通常通过 StateFlow 或 MutableStateFlow 对外公开。夹具需要提供能够驱动这些状态流的容器,并在测试中模拟状态变迁。
典型的夹具状态容器实现:
class ViewModelFixture<T> {
// 状态容器:外部可通过此流观察 ViewModel 的状态
private val _state = MutableStateFlow(null)
val state: StateFlow = _state.asStateFlow()
// 预设初始状态
fun givenState(initial: T) {
_state.value = initial
}
// 模拟状态更新
fun emitState(newState: T) {
_state.value = newState
}
// 清理容器
fun clear() {
_state.value = null
}
}
| 容器类型 | 适用场景 | 生命周期绑定性 |
|---|---|---|
| MutableStateFlow | 需要双向控制状态(读+写) | 随测试作用域结束自动取消 |
| StateFlow | 仅需观察,状态由 ViewModel 内部控制 | 需手动收集,注意取消 |
| LiveData | 与 Android 架构组件兼容 | 需借助 InstantTaskExecutorRule |
3. 生命周期管理
测试夹具的生命周期必须与单个测试用例的生命周期对齐。推荐使用 JUnit Rule 或 TestRule 来统一管理夹具的创建与销毁。
生命周期管理的关键动作:
@Before/create():实例化夹具并注入 ViewModel。@After/clear():释放状态容器、取消协程、清除引用。- 跨测试清理:确保夹具内部不持有静态状态,避免测试间泄漏。
最佳实践:结合
CoroutineTestRule(如 kotlinx-coroutines-test 的 TestDispatcher)可以统一控制 ViewModel 内部协程的生命周期,使夹具与调度器同步。
4. 与 ViewModel 的关系
夹具通过依赖注入(构造函数传参或手动 setter)与 ViewModel 建立关系。典型注入方式:
class MyViewModel(
private val repository: Repository
) : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow = _uiState.asStateFlow()
}
// 夹具注入 ViewModel
class MyViewModelFixture {
private val repository = FakeRepository() // 预置状态
val viewModel = MyViewModel(repository)
fun givenRepositoryReturns(data: List- ) {
repository.setData(data)
}
fun clear() {
viewModel.onCleared()
}
}
这种模式的优点:
- 可预测性:夹具完全控制 ViewModel 的依赖,测试结果可重复。
- 隔离性:每个测试用例独立创建夹具,互不干扰。
- 可组合性:同一个夹具可用于多个 ViewModel,只需调整注入的参数。
常见错误:在夹具内部使用单例或静态状态容器。这会导致测试间状态泄漏,产生“粉饰测试”(tests that pass only when run in a specific order)。
上图展示了夹具如何通过状态容器和生命周期管理两个维度,共同服务于 ViewModel 注入,最终在测试执行和清理回收阶段完成一个完整的测试闭环。每个箭头表示单向依赖或控制关系,体现了夹具在测试中的核心作用:提供可控、可隔离的测试环境。