3. 测试环境初始化:创建AndroidJUnit4测试类、设置ActivityTestRule与ComposeTestRule

3.1 原理

在混合 Espresso + Compose 的测试项目中,测试环境初始化决定了组件能否正确绑定、生命周期是否同步。Android 测试框架通过 Rule 机制为每个测试方法提供可控的 Activity/Compose 运行环境:

  • ActivityTestRule<T>:启动指定的 Activity,并保证 @Before / @After 在生命周期内执行;Espresso 的 onView() 默认在此 Activity 上查找视图。
  • ComposeTestRule(通常使用 createAndroidComposeRule<T>()):同时启动 Activity 并创建 Compose 测试宿主,允许混合调用 compose 的 onNode() 和 Espresso 的 onView()。

两个 Rule 必须通过 @Rule 注解声明,且只能有一个 Activity 实例。如果混用,需保证 ComposeTestRule 排在 ActivityTestRule 之前或使用组合 Rule 避免冲突。

3.2 实现流程

  1. 创建测试类:继承 androidx.test.ext.junit.runners.AndroidJUnit4,使用 @RunWith(AndroidJUnit4::class)。
  2. 声明 Rule:定义 @get:Rule 属性,类型为 ActivityTestRule<MainActivity> 或 createAndroidComposeRule<MainActivity>()。
  3. 初始化被测组件:在 @Before 中通过 Rule 的 activity 属性获取 Activity 实例,或直接使用 Compose 测试语法。
  4. 编写测试方法:标注 @Test,混合使用 Espresso / Compose 断言。
  5. 清理(可选):在 @After 中释放资源。

3.3 代码示例:完整的测试类骨架

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    // 方案A:纯 Espresso(只使用ActivityTestRule)
    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    // 方案B:混合 Compose + Espresso(推荐)
    @get:Rule
    val composeRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun testEspressoOnly() {
        onView(withText("Hello"))
            .check(matches(isDisplayed()))
    }

    @Test
    fun testComposeOnly() {
        composeRule.onNodeWithText("Hello")
            .assertExists()
    }

    @Test
    fun testMixed() {
        // 先通过 Compose 点击
        composeRule.onNodeWithText("Button").performClick()
        // 再用 Espresso 验证 Toast
        onView(withText("Clicked!")).check(matches(isDisplayed()))
    }
}
提示:如果同时声明 ActivityScenarioRule 和 createAndroidComposeRule,请确保 Compose Rule 排在最前面,否则可能因 Activity 重复启动导致 IllegalStateException。

3.4 对比:ActivityTestRule vs ComposeTestRule

特性 ActivityTestRule ComposeTestRule (createAndroidComposeRule)
主要用途 启动 Activity,支持 Espresso 纯 View 测试 启动 Activity + Compose 宿主,支持混合测试
依赖库 androidx.test.espresso:espresso-core androidx.compose.ui:ui-test-junit4
提供的方法 activity、scenario onNode()、onNodeWithText()、activity、setContent()
生命周期控制 自动管理 @Before/@After 自动管理,并额外提供 Compose 的 recomposition 等待
常见错误 Activity 未启动时调用 activity 属性 忘记使用 createAndroidComposeRule 导致无 Compose 环境

3.5 常见错误与调试

  • 错误1:java.lang.IllegalStateException: No activity found —— 在 @Before 中过早调用 activity 属性。解决方案:将初始化逻辑移到 @Test 方法中,或使用 ActivityScenario 的 onActivity 回调。
  • 错误2:同时使用两个 Rule 导致 Activity 启动两次。解决方案:只保留一个 Rule,或确保 ComposeTestRule 是唯一的 Activity 启动入口。
  • 调试技巧:在 @Test 开头添加 Thread.sleep(500) 确保界面渲染完成(仅用于临时调试,不推荐用于生产)。
稳定性优化:在 CI 环境中,建议为 createAndroidComposeRule 设置 useUnmergedTree = false 以提升语义节点匹配速度。同时关闭动画(通过 @get:Rule @JvmField val animationRule = DisableAnimationsRule())。

3.6 测试环境初始化结构图

测试环境初始化结构图(AndroidJUnit4 + 混合 Rule) Test Class @RunWith(AndroidJUnit4) @get:Rule ActivityTestRule<T> @get:Rule createAndroidComposeRule<T> Activity 实例 @Test 方法 混合使用建议 优先使用 ComposeTestRule 避免重复启动 Activity

3.7 部署检查清单

  • 确认 build.gradle 已添加依赖:
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.6.0'
  • 测试类必须位于 src/androidTest 目录下。
  • 确保 @Rule 字段为 public 或使用 @get:Rule 修饰;不能使用 @JvmField 重复。
  • 混合 Rule 时移除 ActivityTestRule,只保留 ComposeTestRule。