夹具(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)。
Fixture、状态容器、生命周期管理与 ViewModel 关系 Fixture 定义 状态容器 生命周期管理 注入 ViewModel 测试执行 清理回收 每个测试用例独立创建 Fixture 保证隔离性与可预测性

上图展示了夹具如何通过状态容器和生命周期管理两个维度,共同服务于 ViewModel 注入,最终在测试执行和清理回收阶段完成一个完整的测试闭环。每个箭头表示单向依赖或控制关系,体现了夹具在测试中的核心作用:提供可控、可隔离的测试环境。