R49第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

组件测试与用户行为

组件测试与用户行为 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完你能用「用户视角」为表单、筛选和对话框写行为测试:按角色和标签查询元素、用 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,演示「查不到」与「抛错」的区别;
  • 验证:依次点过所有查询,理解「测试看到的界面」与「用户看到的界面」是同一棵语义树。

动手任务

打开本课实验区:

  1. 给表单加一个「重置」按钮,为它写出测试三段式(查询 → 操作 → 断言)的伪代码。
  2. 判断三条测试的去留:断言 state.keyword / 断言列表可见文本 / 断言 fetch 调用次数,说出理由。
  3. 把 getByRole 的名称故意写错,观察失败信息里列出的可用角色——读报错是修测试的基本功。

常见错误

  • 测内部状态或 props:重构即碎,且抓不到真实回归;测可见行为。
  • 绕过事件系统直接调函数:绑定错误、冒泡问题全测不到;用 user-event 走真实事件。
  • 断言实现细节(class、style、组件层级):样式重构不算行为变化;确实要测样式时测计算后的可见效果。
  • 测试里 sleep(500) 等待:时序赌博;用 findBy / waitFor 等待「条件成立」,下一课展开。

检查与复盘

自查清单:

  • 每条测试都能回答「用户能看见什么」
  • 查询以 getByRole 为主;查询失败先怀疑语义缺失,而不是换宽松查询
  • 删过至少一条测实现细节的测试

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

测试一个「点击按钮数字加一」的计数器组件,下列断言最符合「测行为不测实现」原则的是?

Q2多选中等(多选,全部选对才算正确)

TodoList 组件:输入框 + 添加按钮,点添加后新待办出现在列表里且输入框清空。下列断言组合中合理且稳定的有哪些?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...