用途
Testing with Espresso Interop 技能用于在同一 Activity 上同时使用 Compose 测试规则(ComposeTestRule)和 Espresso 的 onView API,把两种测试框架桥接在一起。其核心要点是:Compose 的同步机制(idling resources、frame clock 等待)通过一个名为 EspressoLink 的内部 IdlingResource 桥接到 Espresso,开发者无需手动注册。
适用场景
以下情况应使用该技能:
- 测试需要操作 Android Dialog 窗口,该窗口位于独立 Window 中,可能不在 Compose 语义树内。
- 测试需要验证 IME(软键盘)状态;只有 Espresso 的 onView(supportsInputMethods()).perform(click()) 才能让键盘出现。
- 被测屏幕是混合 Activity,包含装有 ComposeView 的 Android View 层级,或反之。
- 开发者询问「如何在同一个测试中使用 Espresso 和 Compose」。
- 测试在 runOnIdle { } 中调用 Espresso.onView(...) 导致挂起或抛出「cannot be run from the main thread」。
不适用场景
- 测试是纯 Compose,不存在 Android View 交互。应使用 structuring-a-compose-test 技能。
- 同步问题表现为「本地通过、CI 上不稳定」,但不涉及 View 互操作。应使用 synchronizing-with-idle 技能。
- 测试针对 androidx.compose.ui.window.Dialog(Compose 对话框),它通过 isDialog() 出现在语义树中,不需要 Espresso。应使用 composing-semantics-matchers 技能。
前置条件
- androidTestImplementation 中包含 androidx.test.espresso:espresso-core。
- androidTestImplementation 中包含 androidx.compose.ui:ui-test-junit4,debugImplementation 中包含 androidx.compose.ui:ui-test-manifest。
- 一个同时承载两层的 Activity,标准选择是 FragmentActivity;也可用在 src/androidTest/AndroidManifest.xml 中声明的自定义 Activity。
- composing 测试类骨架。
工作流
- 对宿主 Activity 使用 createAndroidComposeRule<A>()(v2 版本)。示例:val rule = createAndroidComposeRule<FragmentActivity>(StandardTestDispatcher())。
- 用 rule.setContent { } 设置 Compose 内容,然后调用 rule.waitForIdle(),排空 Compose recomposer/effect 队列,使后续 Espresso 交互看到稳定的视图树。
- 从测试线程通过 Espresso.onView(...) 驱动 View 层,不要将该调用包在 runOnIdle 或 runOnUiThread 中。
- 在测试线程上自由混合 Compose 和 Espresso 断言,两个 API 针对同一 Activity 运行。
- (可选)当 Activity 包含多个 Compose 宿主时,用 onRootWithViewInteraction(viewInteraction) 将 Compose 查找器限定到由 Espresso 解析的子树。此方法仅在 AndroidComposeTestRule 上支持,非 Android 测试规则会抛出「This implementation of ComposeTestRule does not support onRootWithViewInteraction.」。
- 信任 EspressoLink 桥接:Compose 的空闲资源由 ComposeIdlingResource 聚合,通过名为「Compose-Espresso link」的单一 IdlingResource 暴露给 Espresso,框架会在 withStrategy { } 内自动注册和注销,开发者无需为 Compose 状态调用 IdlingRegistry.getInstance().register(...)。
- 反之,通过 IdlingRegistry.getInstance().register(…) 注册的 Espresso 空闲资源对 Compose 也可见,rule.waitForIdle() 会等待它们。
常见模式与错误
模式一:从 runOnIdle 调用 Espresso.onView 造成死锁。因为 runOnIdle 会 post 到 UI 线程,而 Espresso.onView 内部会调用 Espresso.onIdle(),EspressoLink.runUntilIdle 明确在 UI 线程调用时抛错。正确做法是在 rule.waitForIdle() 之后于测试线程直接调用 Espresso.onView。
模式二:用 Espresso 处理对话框焦点、用 Compose 处理内容。Android Dialog 位于独立 Window,即使 TextField 请求焦点,软键盘也不会出现,直到对话框窗口本身获得输入焦点,这只能由 Espresso 的 onView(supportsInputMethods()).perform(click()) 驱动。
模式三:createComposeRule() 无法访问 Activity。它启动的是 ui-test-manifest 中的空 ComponentActivity;要交互自定义 Activity 的视图层级,必须用 createAndroidComposeRule<MyActivity>() 启动该 Activity。
强制规则
- 必须在 rule.waitForIdle() 之后从测试线程调用 Espresso.onView(...),不得包在 runOnIdle 或 runOnUiThread 中。
- 任何需要访问宿主 Activity View ID 的测试必须使用 createAndroidComposeRule<A>(StandardTestDispatcher())。
- 不得手动通过 IdlingRegistry.getInstance() 注册 Compose IdlingResource,EspressoLink 已代为处理。
- 当一个 Activity 承载多个 ComposeView 时,推荐用 rule.onRootWithViewInteraction(viewInteraction) 限定 Compose 交互范围。
- 软键盘测试推荐流程:rule.setContent { … } → rule.waitForIdle() → Espresso.onView(supportsInputMethods()).perform(click()) → keyboardHelper.waitForKeyboardVisibility(visible = true)。
- 不得对 androidx.compose.ui.window.Dialog(Compose 对话框)使用此技能,此类对话框以语义节点形式出现,由 isDialog() 匹配。
验证清单
- 规则是 createAndroidComposeRule<HostActivity>(StandardTestDispatcher()),而非 createComposeRule()。
- 每个 Espresso.onView(...) 调用都在测试方法作用域,不在 runOnIdle / runOnUiThread / setContent 内。
- 在 Compose 动作与下一个 Espresso.onView(...) 之间调用 rule.waitForIdle()(或反之)。
- 没有手动 IdlingRegistry.getInstance().register(composeIdlingResource) 调用。
- 当一个 Activity 存在多个 ComposeView 时,查找器通过 rule.onRootWithViewInteraction(...) 限定范围。
- 不使用 Thread.sleep 来「等 Espresso 稳定」,rule.waitForIdle() 已通过桥接轮询 Espresso。
参考
- Espresso onView API: https://developer.android.com/training/testing/espresso/basics
- Compose-Espresso 互操作指南: https://developer.android.com/develop/ui/compose/testing#interop-uiautomator-espresso