React Testing 是一套面向 React 组件测试的实践技能,目标是帮助开发者编写以行为为中心的组件测试、自定义 Hook 测试、无障碍断言以及网络层模拟。
【用途】 当需要为 React 组件、自定义 Hook 或页面编写测试;为遗留的未测试组件补充测试覆盖;从 Enzyme 或类组件时代的测试模式迁移到 React Testing Library;为新 React 项目配置 Vitest 或 Jest;在测试中模拟 HTTP 请求;断言无障碍违规;或判断某个测试应归入 RTL、Playwright 组件测试还是完整端到端测试时,可以启用本技能。
【核心原则】 测试用户看到什么、做什么,而不是实现细节。测试应当:使用与生产环境相同的 Provider 渲染组件;通过可访问的查询(role、label)和 userEvent 进行交互;断言可见输出与可观察的副作用(回调被调用、请求已发出)。测试不应:检查组件 state、传给子组件的 props 或调用了哪些 Hook;模拟 React 本身或框架 Hook;断言渲染次数或对用户无影响的 DOM 结构。
【测试运行器选择】 Vitest 适合 Vite、Remix 和现代配置,更快、原生 ESM、API 与 Jest 兼容。Jest 适合 Next.js、CRA 和已有仓库,是许多 React 项目的默认选择。需要真实浏览器引擎时使用 Playwright Component Testing;已在使用 Cypress 且需要真实浏览器时可用 Cypress Component Testing。三者择一,除非有清晰的职责划分,否则不要在同一仓库同时运行 RTL + Vitest 与 Playwright CT。
【查询优先级】 RTL 查询分三层,自上而下使用:面向所有人的可访问查询(getByRole、getByLabelText、getByPlaceholderText、getByText、getByDisplayValue);语义查询(getByAltText、getByTitle);测试 ID(逃生通道,getByTestId)。变体包括:getBy* 无匹配时抛错;queryBy* 返回 null,用于断言不存在;findBy* 为异步,返回 Promise,用于异步工作后出现的元素。
【用户交互】 使用 userEvent,始终 await 调用,每个测试调用一次 userEvent.setup() 并复用返回的 user。userEvent 模拟真实浏览器序列,fireEvent 只派发单个合成事件,应优先 userEvent。
【异步模式】 异步元素使用 await screen.findByText(...),副作用断言使用 await waitFor(...),元素消失使用 waitForElementToBeRemoved。不要用 setTimeout 加断言,容易产生不稳定测试。
【MSW 网络模拟】 Mock Service Worker 在网络层模拟,组件、Hook 和请求库的行为与生产一致。用 setupServer 配置 handlers,beforeAll 调用 server.listen({ onUnhandledRequest: "error" }),afterEach 调用 resetHandlers,afterAll 关闭 server。将未匹配请求设为报错,可让漏配的请求显式失败。每个测试可用 server.use 覆盖 handler。
【Provider 包装】 建议在 test-utils.tsx 中集中包装 Provider,导出 renderWithProviders,并在每个测试文件中从 test-utils 引入 renderWithProviders 和 screen。
【自定义 Hook 测试】 使用 renderHook 和 act。状态变更调用包在 act 中,只通过 Hook 的公开 API 测试,使用 context 的 Hook 需传入 wrapper。注意在测试内、wrapper 外只创建一次 QueryClient,否则每次渲染都会重置缓存,导致测试不稳定。
【无障碍断言】 使用 jest-axe 或 vitest-axe,配合 toHaveNoViolations。在每个交互组件测试中运行 axe,可发现表单缺少标签、ARIA 使用不当、图片缺少 alt 文本、标题层级错误等问题。由于 JSDOM 没有真实 CSS 引擎,颜色对比检测仅对行内样式有限有效,视觉对比应交给 Playwright。
【快照测试的取舍】 渲染输出的快照会随每次样式变化而失败,评审时容易被机械放过,且测试的是 DOM 结构而非行为。可接受的快照用途包括:纯数据序列化函数、生成的配置文件。组件视觉回归应使用 Playwright/Cypress 截图或 Percy/Chromatic。
【何时使用 Playwright / Cypress】 JSDOM 无法渲染真实布局(flexbox、grid、视口查询)、运行原生浏览器动画与 CSS 过渡、测试滚动、拖放、从剪贴板粘贴、处理 iframe、弹窗、下载、跨域流程,以及带完整 DevTools 支持的真实网络。这些场景应使用 Playwright 组件测试或完整 E2E。决策边界:Hook、展示型组件、带逻辑的表单用 RTL;布局重要或使用 JSDOM 不支持的浏览器 API 的组件用 Playwright CT;跨多个页面的完整用户流程用 Playwright/Cypress E2E。
【覆盖率目标】 纯工具函数 >=90%,自定义 Hook >=85%,展示型组件 >=80%(关注行为而非行数),容器组件 >=70%(关键路径加错误状态),页面由 E2E 单独覆盖,至少做冒烟测试。可在 vitest.config.ts 或 jest.config.js 中配置 lines、functions、branches、statements 阈值。
【反模式】 使用 container.querySelector 绕过可访问性查询;断言渲染次数;用 jest.mock("react", ...) 模拟 React;默认模拟子组件;忽略 act() 警告;测试间共享可变状态;以及去掉 it.skip() 后仍然通过的测试——说明测试并未真正断言预期内容。
【TDD 工作流与命令】 流程为 RED(为下一项需求写失败测试)、GREEN(写最小实现通过)、REFACTOR(改进组件,测试保持绿色)、REPEAT。常用命令包括 vitest(监听)、vitest run、vitest run --coverage、vitest run 指定文件;Jest 对应 jest --watch、jest --coverage 等;CI 模式为 CI=true vitest run --coverage。本技能还与 react-patterns、accessibility、e2e-testing、tdd-workflow 等技能,以及 react-reviewer、tdd-guide 等 Agent 和 /react-test、/react-review 命令相关联,并提供表单提交与 MSW/userEvent、错误边界、Suspense 边界等示例。