1. Compose 测试夹具概述

1.1 测试夹具(Test Fixture)定义

测试夹具是测试执行前所需的固定环境与状态的总和。在传统单元测试中,夹具表现为 @Before / @After 方法、模拟对象、数据库连接或文件系统目录。在 Compose UI 测试中,夹具的核心是 可组合函数(Composable)的宿主环境——包括 ComposeTestRule、Activity 容器、主题、语义树等基础设施。

核心认知:Compose 测试夹具不是简单的“数据准备”,而是为声明式 UI 提供完整的运行时沙箱,使每次测试都在隔离的、可预测的 Compose 环境中执行。

1.2 Compose UI 测试的特殊性

与传统 View 体系 UI 测试相比,Compose UI 测试有三大根本差异:

维度 传统 View 测试 Compose UI 测试
组件树构建 依赖 Activity/Fragment 生命周期,手动触发渲染 由 Compose 运行时自动重组,状态驱动渲染
交互方式 通过 onView() 匹配 View ID 通过语义属性(onNodeWithText、onNodeWithTag)匹配
测试执行模型 同步断言,需显式 IdlingResource 处理异步 基于协程和快照系统,提供内置 waitForIdle() 同步点
环境依赖 需要完整的 Instrumentation 和 Activity 生命周期 可通过 createComposeRule 在轻量级宿主中运行

这些特殊性要求测试夹具必须为 Compose 运行时提供:重组调度器、语义树同步器、协程作用域以及快照状态容器。

1.3 夹具在测试中的角色

一个完整的 Compose UI 测试夹具承担以下职责:

  1. 环境初始化:创建 ComposeTestRule 实例,绑定 Activity 或 ComposeHost;
  2. 组件挂载:通过 setContent 将待测 Composable 挂载到宿主树;
  3. 依赖注入:提供 ViewModel、Repository 等模拟依赖(常与 Hilt、Koin 配合);
  4. 状态同步:确保 Compose 重组完成后再执行断言,避免竞态;
  5. 资源清理:测试结束后释放 Compose 环境,避免泄漏影响后续测试。
夹具设计原则:每个测试用例应当拥有独立的夹具实例,避免共享可变状态。推荐使用 @get:Rule 注解 ComposeTestRule,由 JUnit 规则引擎自动管理生命周期。

1.4 Compose 测试夹具架构总览

下方流程图展示了测试夹具在 Compose UI 测试中的位置及内部关键组件:

Compose 测试夹具架构图 测试类 (Test Class) @Rule + @get:Rule ComposeTestRule setContent { … } UI 组件树 (Recompose) 语义树 (Semantics Tree) 断言 (Assertions) 夹具边界:虚线框内为测试夹具负责管理的内容 规则生命周期 → ComposeTestRule 创建 → setContent 挂载 → 重组同步 → 断言后清理

1.5 最小夹具实现示例

以下代码片段展示了一个完整的 Compose 测试夹具骨架:

import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
import org.junit.Test

class CounterTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun testCounterIncrement() {
        // 夹具:通过 setContent 挂载待测 Composable
        composeTestRule.setContent {
            Counter() // 假设已定义的 Composable
        }

        // 夹具同步等待重组完成
        composeTestRule.waitForIdle()

        // 断言:通过语义树验证状态
        composeTestRule
            .onNodeWithText("Count: 0")
            .assertExists()
    }
}
常见陷阱:
• 不要在 setContent 外创建与 Compose 状态绑定的变量,否则会导致重组不同步;
• 务必使用 waitForIdle() 或 awaitIdle() 等待重组完成,否则断言可能跑在中间态;
• 每个测试类应只使用一个 @get:Rule 注解的 ComposeTestRule,避免规则冲突。