第4章 Compose测试规则详解:createComposeRule vs createAndroidComposeRule、Activity设置、自定义Content

4.1 概念与作用域

Compose UI测试的核心入口是ComposeTestRule接口,它负责管理Composable的合成生命周期、调度帧、等待空闲状态以及提供语义树查询。Jetpack Compose测试库提供了两个具体实现类:

  • createComposeRule() — 轻量级规则,仅启动Compose环境,不依赖Activity生命周期;适用于纯组件级测试。
  • createAndroidComposeRule<Activity>() — 全栈规则,启动一个真实Activity并嵌入ComposeContent;可用于需要Activity生命周期、Context、资源、Intent的集成测试。
核心原则: 组件级测试首选createComposeRule,集成测试(需Activity、Fragment、导航)必须使用createAndroidComposeRule。

4.2 规则选择对比表

维度createComposeRulecreateAndroidComposeRule<A>
启动速度快(无Activity创建)中等(需启动Activity)
Activity生命周期无完整(onCreate、onStart等)
Context / 资源仅Compose环境真实Activity Context
Intent / 权限不支持支持(需Instrumentation)
Fragment / Navigation需额外模拟原生支持
适用场景单元测试、组件快照UI集成测试、端到端

4.3 Activity设置详解

当使用createAndroidComposeRule时,可以通过泛型参数指定Activity类型。默认启动ComponentActivity,也可指定自定义Activity:

@Rule
@JvmField
val composeTestRule = createAndroidComposeRule<MainActivity>()

若需要控制Activity的Intent或启动参数,使用launchActivity方法:

composeTestRule.activityScenario.launch(
    Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java).apply {
        putExtra("test_key", "test_value")
    }
)

4.3.1 Activity设置流程

  1. 规则初始化时通过ActivityScenario创建Activity实例。
  2. Activity的onCreate中通常调用setContent,该Content由规则内部维护。
  3. 可通过composeTestRule.activity获取当前Activity引用,用于断言或触发系统行为。
避坑: createAndroidComposeRule中的Activity只能启动一次,若测试中需要重新创建Activity(如旋转屏幕),需使用ActivityScenario.recreate(),并在composeTestRule.waitForIdle()后继续断言。

4.4 自定义Content:setContent与自定义Composable

无论是哪种规则,都需要通过setContent加载被测Composable。自定义Content通常指在测试中组装特定的UI层次,包括:

  • 提供主题(MaterialTheme、自定义Theme)
  • 注入依赖(ViewModel、Repository)
  • 包裹容器(Box、Scaffold、NavHost)
  • 设置状态(remember、mutableStateOf)

4.4.1 基础用法

composeTestRule.setContent {
    // 自定义Content
    MyButton(
        text = "Submit",
        onClick = { /* 测试逻辑 */ }
    )
}

4.4.2 带主题与依赖注入的常用模式

@Test
fun testButtonWithTheme() {
    composeTestRule.setContent {
        AppTheme(darkTheme = false) {
            Surface {
                val viewModel = remember { FakeViewModel() }
                MyButton(viewModel = viewModel)
            }
        }
    }
    // 断言...
}

4.4.3 自定义Content的职责边界

职责示例说明
环境搭建主题、Surface、Box确保Composable在真实环境中渲染
数据层模拟FakeViewModel、TestRepository隔离外部依赖,控制测试数据
嵌套组件NavHost、Scaffold测试导航或脚手架场景
状态容器mutableStateOf、StateFlow模拟用户交互导致的状态变化
实践建议: 将自定义Content提取为独立的@Composable函数(如TestContent),方便复用并减少测试代码冗余。

4.5 规则初始化流程与生命周期

ComposeTestRule 初始化与自定义Content流程 测试方法 @Test 开始 选择规则类型 createComposeRule() createAndroidComposeRule() setContent { … } Activity+setContent { … } ! 轻量,无Activity 完整Activity生命周期

4.6 实现流程:从规则声明到断言

  1. 声明规则 — 使用@get:Rule注解(JUnit4)或@TestRule(JUnit5)。
  2. 设置Content — 在@Before或每个测试方法中调用setContent,传入自定义Composable。
  3. 触发行为 — 使用onNodeWithText()、performClick()等语义操作。
  4. 等待空闲 — waitForIdle()或awaitIdle()确保Compose动画与重组完成。
  5. 断言结果 — 通过assertIsDisplayed()、assertTextEquals()或自定义断言。
@get:Rule
val rule = createComposeRule()

@Test
fun customContentTest() {
    rule.setContent {
        val count = remember { mutableStateOf(0) }
        Button(onClick = { count.value++ }) {
            Text("Click $count")
        }
    }

    rule.onNodeWithText("Click 0").assertExists()
    rule.onNodeWithTag("button").performClick()
    rule.waitForIdle()
    rule.onNodeWithText("Click 1").assertExists()
}

4.7 常见错误与调试

  • “No compose nodes found” — Content未正确set或使用了错误的onNode匹配器。先通过printToLog()打印完整语义树。
  • “Activity destroyed” — 在异步回调中访问activity;使用activityScenario.onActivity { }确保在UI线程访问。
  • “Compose content is not set” — 忘记调用setContent;检查@Before或测试方法体内的调用顺序。
  • “Multiple rules” — 声明了多个@Rule未按顺序执行;使用RuleChain控制执行顺序。

4.8 性能与稳定性优化

策略说明适用场景
复用规则实例一个@ClassRule共享一个规则,减少重复启动多个测试共享相同Content
使用waitForIdle超时避免测试因动画卡死;设置timeoutMillis含动画或异步网络
避免真实依赖用Fake/Test双代替Repository,减少I/O单元测试场景
减少Content重建在@Before中统一setContent,不要在每条测试中重复批量常规测试
关键总结:
  • createComposeRule适用于纯Composable单元测试,最快最轻。
  • createAndroidComposeRule适用于集成测试,需Activity参与。
  • 自定义Content是测试的“舞台”,通过主题、依赖、容器构建真实测试环境。
  • 善用语义树打印与超时控制,提升调试效率和稳定性。