组件测试与用户行为
组件测试与用户行为 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能用「用户视角」为表单、筛选和对话框写行为测试:按角色和标签查询元素、用 user-event 模拟真实交互、断言用户能看见的结果;并能识别和清理「依赖内部实现」的脆测试。
问题场景
筛选表单的测试第一版:expect(component.state.keyword).toBe("react")。重构时把 state 改名 searchInput,功能毫发无伤,测试红了三条。测试的目的本是「重构时兜底」,结果它自己成了重构的阻力——因为它盯着的不是用户行为,而是实现细节。用户看不见 state、看不见 class 名、看不见组件内部结构;测试也应该看不见。
核心解释
行为测试的三段式:以用户的方式查询,以用户的方式操作,断言用户能看见的东西:
test("输入关键词后列表只显示匹配课程", async () => {
render(<CourseFilter courses={courses} />);
// 查询:按「角色 + 名称」,与辅助技术用同一套语义
await userEvent.type(screen.getByRole("textbox", { name: "搜索课程" }), "react");
// 断言:可见文本,而不是内部状态
expect(screen.getByText("React 入门")).toBeInTheDocument();
expect(screen.queryByText("状态管理")).not.toBeInTheDocument();
});
三条要点:
**一、查询优先级:getByRole 第一。**getByRole 顺带验证了可访问性语义——元素没有正确的角色和名称,查询就会失败。测试与上一课的无障碍实践互相成就;getByText、getByLabelText 次之;测试类名、style、props 属于实现细节,默认不碰。
**二、交互用 user-event,别绕过事件系统。**userEvent.type 会真实触发 focus、keydown、input 全链路,和用户行为一致:
// 推荐:真实交互链路(focus → keydown → input → change)
await userEvent.type(screen.getByRole("textbox", { name: "搜索课程" }), "react");
// 不推荐:绕过 DOM 事件系统,监听器绑没绑上都测不出来
fireEvent.change(screen.getByRole("textbox"), { target: { value: "react" } });
三、断言结果,不断言路径。「提交后显示成功提示」是行为;「提交后 fetch 被调了一次、state 变成 X」是路径。路径测试在合法重构下碎裂,行为测试在等价重构下存活。
脆测试的改造判断:测试失败时先问「用户可感知的行为变了吗」——没变而测试红了,是测试在测实现,删改测试;变了而测试没红,是覆盖缺口,补断言。
示例拆解
实验区把「测试如何看组件」做成了可视化:左边是一个筛选表单,右边是一组「测试查询」按钮:
- 点
getByRole("textbox", { name: "搜索课程" })→ 实际命中的输入框被高亮;注意 label 包住 input 后,输入框的可访问名称就是 label 文本; - 点
getByRole("button", { name: "提交筛选" })→ 高亮提交按钮; - 点
getByText("React 入门")→ 高亮命中的列表项;点queryByText("不存在的课程")→ 返回 null,演示「查不到」与「抛错」的区别; - 验证:依次点过所有查询,理解「测试看到的界面」与「用户看到的界面」是同一棵语义树。
动手任务
打开本课实验区:
- 给表单加一个「重置」按钮,为它写出测试三段式(查询 → 操作 → 断言)的伪代码。
- 判断三条测试的去留:断言 state.keyword / 断言列表可见文本 / 断言 fetch 调用次数,说出理由。
- 把 getByRole 的名称故意写错,观察失败信息里列出的可用角色——读报错是修测试的基本功。
常见错误
- 测内部状态或 props:重构即碎,且抓不到真实回归;测可见行为。
- 绕过事件系统直接调函数:绑定错误、冒泡问题全测不到;用 user-event 走真实事件。
- 断言实现细节(class、style、组件层级):样式重构不算行为变化;确实要测样式时测计算后的可见效果。
- 测试里 sleep(500) 等待:时序赌博;用 findBy / waitFor 等待「条件成立」,下一课展开。
检查与复盘
自查清单:
- 每条测试都能回答「用户能看见什么」
- 查询以 getByRole 为主;查询失败先怀疑语义缺失,而不是换宽松查询
- 删过至少一条测实现细节的测试
参考资料
- Testing Library:React 测试入门 ——「以用户使用的方式测试」的核心理念
- Testing Library:ByRole 查询 —— 角色查询与可访问性语义
- React 官方文档:act —— 测试环境下 React 更新的同步原语
读完这一课了?
标记完成后,工作台会安排接下来的学习。