6、状态验证:assertIsDisplayed、assertTextEquals、assertExists
状态验证,说白了就是检查 UI 组件「对不对、在不在、显不显」。
我刚开始写 Compose 测试时,总觉得只要点一下按钮、输入几个字就完事了。后来发现,没有状态验证的测试就像闭着眼睛开车——你根本不知道 UI 到底呈现了什么。今天我们就来聊聊三个最核心的验证方法。
6.1 验证组件是否存在:assertExists
这是最基础的验证。你只需要确认某个组件确实被渲染到了屏幕上。
@Test
fun testButtonExists() {
composeTestRule.setContent {
Button(onClick = { }) {
Text("提交")
}
}
composeTestRule
.onNodeWithText("提交")
.assertExists()
}
嗯,这里要注意:assertExists 只检查组件是否在组件树中,不关心它是否可见。我遇到过一种情况——组件确实存在,但被其他布局挡住了,测试依然通过。所以它适合做「快速冒烟检查」。
- 检查关键按钮是否渲染
- 检查列表项是否生成
- 检查动态加载的组件是否出现
6.2 验证组件是否可见:assertIsDisplayed
这个就比 assertExists 严格多了。它不光检查组件存在,还检查组件是否真正可见——说白了就是用户能不能看到它。
@Test
fun testDialogDisplayed() {
composeTestRule.setContent {
var showDialog by remember { mutableStateOf(true) }
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("提示") },
text = { Text("操作成功") },
confirmButton = { TextButton(onClick = { showDialog = false }) { Text("确定") } }
)
}
}
composeTestRule
.onNodeWithText("操作成功")
.assertIsDisplayed()
}
我个人习惯用 assertIsDisplayed 代替 assertExists,除非有特殊原因。为什么?因为用户只关心「能不能看到」,不关心「存不存在」。你想想看,一个被 Modifier.alpha(0f) 隐藏的组件,assertExists 会通过,但用户根本看不见。
assertIsDisplayed对Modifier.size(0.dp)的组件会失败- 对
Modifier.alpha(0f)的组件也会失败 - 对
Modifier.offset(10000.dp)移出屏幕的组件同样失败
6.3 验证文本内容:assertTextEquals
文本验证是 UI 测试里最频繁的操作。我见过有人用 assertExists 检查文本,但那样只能确认「有文字」,不能确认「是什么文字」。
@Test
fun testGreetingText() {
composeTestRule.setContent {
var name by remember { mutableStateOf("小明") }
Text("你好,$name")
}
composeTestRule
.onNodeWithText("你好,小明")
.assertTextEquals("你好,小明")
}
这里有个细节:assertTextEquals 默认是精确匹配。如果你只想匹配部分内容,可以用 assertTextContains。我曾经在测试一个长文本时,因为末尾多了一个空格,测试挂了半小时才找到原因——嗯,从那以后我写文本验证都会先打印一下实际内容。
在测试里加一句 .printToLog("TEST"),可以打印出当前节点的所有文本信息。我每次写新测试都会先用它看看实际内容长什么样。
6.4 三种验证的对比
| 方法 | 检查内容 | 严格程度 | 典型场景 |
|---|---|---|---|
| assertExists | 组件是否在树中 | 低 | 快速冒烟、检查动态组件是否生成 |
| assertIsDisplayed | 组件是否可见 | 中 | 弹窗、加载状态、用户交互元素 |
| assertTextEquals | 文本内容是否匹配 | 高 | 表单验证、错误提示、数据展示 |
6.5 组合使用:一个完整的测试案例
实际项目中,很少只用一种验证。我一般会组合使用,像这样:
@Test
fun testLoginFormValidation() {
composeTestRule.setContent {
LoginScreen()
}
// 1. 先确认表单组件存在且可见
composeTestRule
.onNodeWithTag("username_input")
.assertIsDisplayed()
composeTestRule
.onNodeWithTag("password_input")
.assertIsDisplayed()
// 2. 输入错误信息
composeTestRule
.onNodeWithTag("username_input")
.performTextInput("")
composeTestRule
.onNodeWithTag("login_button")
.performClick()
// 3. 验证错误提示文本
composeTestRule
.onNodeWithText("用户名不能为空")
.assertIsDisplayed()
.assertTextEquals("用户名不能为空")
}
你看,这个测试里三种验证都用上了。先确认输入框可见,再点击按钮,最后验证错误提示的内容和可见性。每一步都有明确的目的。
6.6 知识体系总览
下面这张图总结了状态验证的核心逻辑:
6.7 常见问题与避坑
Q:为什么 assertExists 通过了,但 UI 上就是看不到?
大概率是组件被 Modifier.alpha(0f) 或 Modifier.size(0.dp) 隐藏了。换成 assertIsDisplayed 就能发现。
Q:assertTextEquals 总是失败,明明文字看起来一样?
检查一下是否有不可见字符,比如空格、换行符。我建议先用 .printToLog() 打印实际内容,肉眼对比一下。
Q:多个相同文本的组件怎么验证?
用 onAllNodesWithText() 获取列表,然后按索引验证。或者给组件加 testTag,用 onNodeWithTag() 精确定位。
写状态验证时,记住一个原则:先确认存在,再确认可见,最后确认内容。这个顺序能帮你快速定位问题。如果 assertExists 都过不了,就别浪费时间检查可见性和文本了。
公众号:蓝海资料掘金营,微信deep3321