ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React 组件测试实战:用 Jest 与 React Testing Library 编写 UI 测试(The Odin Project 课程精讲)

React 组件测试实战:用 Jest 与 React Testing Library 编写 UI 测试(The Odin Project 课程精讲) React 组件测试实战用 Jest 与 React Testing Library 编写 UI 测试The Odin Project 课程精讲【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum导读本篇技术指南围绕 The Odin Project 开源课程curriculum 仓库中的 React 测试入门课程展开系统讲解在 React 应用中引入 Jest 与 React Testing Library 的完整流程从测试环境所需的依赖包、首次查询render / screen / getByRole到用userEvent模拟真实用户点击再到快照测试的原理、优点与陷阱。学完本篇你将能够独立为一个 React 组件搭建测试环境、编写可维护的 UI 断言并准确判断何时该用快照、何时不该用快照。本篇文章的主体为旧版课程文档 archive/javascript/react_js/react_testing_part_one.md并以仓库中同主题的新版课程 react/react_testing/introduction_to_react_testing.md 及第二部分 archive/javascript/react_js/react_testing_part_two.md 作为纵深补充帮助你同时理解 Jest 与 Vitest 两套主流工具链下的 React 测试写法。为什么需要 UI 测试在进入具体 API 之前先明确一个前提之前的课程例如 javascript/testing_javascript/testing_basics.md已经介绍了 Jest 与测试驱动开发TDD的基础。但当时所写的测试大多只覆盖纯 JavaScript 逻辑——比如战舰Battleship游戏的核心规则——并没有真正把 DOM 渲染出来。正如仓库新版课程 react/react_testing/introduction_to_react_testing.md 所指出的底层逻辑测试通过并不代表 UI 真的显示了预期内容也不代表用户能按预期与页面交互。你可能把某个状态结构从对象改成了数组结果列表不再显示预期的文字也可能在组件里加了一个条件分支导致拖拽卡片的最后几个放置目标失效。这些 bug 只有把 DOM 纳入测试范围才能暴露出来。UI 测试的价值正在于此它验证网页是否包含预期内容、行为是否符合预期并在需求不再被满足时第一时间发出告警。随着项目变复杂UI 测试连同非 UI 测试的价值只会越来越大。测试一个 React 应用需要哪些包课程原文档在 Setting Up 一节明确列出了在测试文件中需要引入的依赖import React from react; import { ... } from testing-library/react; import testing-library/jest-dom; // optional import userEvent from testing-library/user-event; import TestComponent from path-to-test-component;各包的职责如下testing-library/react提供核心 API最常用的是render函数——把组件渲染到内存 DOM 中供测试断言使用。testing-library/jest-dom提供一组便捷的自定义匹配器断言函数例如toBeInTheDocument。Jest 本身已经内置大量匹配器因此这个包并非强制使用但能显著提升断言的可读性。testing-library/user-event提供userEventAPI用于模拟用户与网页的交互点击、键入、聚焦等。其备选方案是从testing-library/react导入的fireEventAPI。课程原文档特别强调fireEvent是userEvent的劣化替代品实践中应始终优先使用userEvent。jest无需显式导入Jest 会自动检测*.test.js或*.test.jsx后缀的测试文件。好消息是如果你使用create-react-app初始化 React 项目上述所有包都已预装package.json中的测试脚本也已预先配置好直接npm test即可。新旧课程工具链对比Jest 与 Vitest值得注意的是仓库中的课程本身也在演进。旧版课程本篇主体基于Jest而新版课程 react/react_testing/introduction_to_react_testing.md 在引入 Vite 后切换到了Vitest与 Vite 深度集成的测试运行器。两者在测试写法上高度同构旧版jest.fn()、describe/it/expect来自 Jest 全局环境新版vi.fn()、describe/it/expect需要从vitest显式导入。新课程还强调即使你在vite.config.js中设置了globals: trueESLint 仍可能不识别这些全局变量最直接的解决办法是改为在测试文件中显式导入所需全局变量此时甚至可以省略globals: true。此外新课程的安装命令明确为npm install testing-library/user-event --save-dev并且其运行环境依赖jsdom——它在内存中模拟 DOM含事件系统但并不真正进行浏览器式的视觉布局这让测试既能解析 DOM 内容又足够轻量。第一次查询render 与 screen掌握了依赖包之后先写第一个测试。课程原文档给出了一个最简组件及其测试// App.js import React from react; const App () h1Our First Test/h1; export default App;// App.test.js import React from react; import { render, screen } from testing-library/react; import App from ./App; describe(App component, () { it(renders correct heading, () { render(App /); expect(screen.getByRole(heading).textContent).toMatch(/our first test/i); }); });在终端执行npm test App.test.js测试即可通过。拆解这段代码render(App /)把组件渲染到测试环境中。render返回一个对象你可以通过解构获取其中的方法例如container。更推荐的做法是使用下面要讲的screen。screen.getByRole(heading)按 ARIA 角色查询标题元素。getByRole只是 React Testing Library 十几种查询方法之一。toMatch(/our first test/i)正则表达式配合i标志实现大小写不敏感的文本匹配这正是课程新版文档 react/react_testing/introduction_to_react_testing.md 中强调的写法。查询的三种类型getBy / queryBy / findByReact Testing Library 的查询方法按返回行为分为三类getBy*找不到元素时直接抛出错误用于断言元素必然存在queryBy*找不到时返回null适合断言元素不存在findBy*异步查询返回一个 Promise适合等待元素在异步渲染后出现。为什么 ByRole 查询是首选课程原文档特别强调ByRole系列方法是查询的首选尤其是配合name选项使用。例如上面的断言可以增强特异性getByRole(heading, { name: Our First Test })ByRole查询的价值在于它基于元素的语义角色而非实现细节如 class 名因此无论用户通过鼠标、键盘还是辅助技术屏幕阅读器等导航页面查询到的都是同一个可访问的 UI 结构。换句话说按角色查询天然保证了无障碍性。如果内置查询方法都不够用React Testing Library 还提供了data-testid兜底方案ByTestId查询但应作为最后手段。模拟用户事件userEvent用户与网页的交互方式多种多样。虽然真实用户反馈不可替代但通过测试我们仍能为组件建立相当程度的信心。课程原文档给出了一个点击按钮改变标题的组件// App.js import React, { useState } from react; const App () { const [heading, setHeading] useState(Magnificent Monkeys); const clickHandler () { setHeading(Radical Rhinos); }; return ( button typebutton onClick{clickHandler} Click Me /button h1{heading}/h1 / ); }; export default App;对应的测试套件如下// App.test.js import React from react; import { render, screen } from testing-library/react; import userEvent from testing-library/user-event; import App from ./App; describe(App component, () { it(renders magnificent monkeys, () { // since screen does not have the container property, well destructure render to obtain container for this test const { container } render(App /); expect(container).toMatchSnapshot(); }); it(renders radical rhinos after button click, async () { const user userEvent.setup(); render(App /); const button screen.getByRole(button, { name: Click Me }); await user.click(button); expect(screen.getByRole(heading).textContent).toMatch(/radical rhinos/i); }); });这个测试套件透露了三个关键实践优先使用screen而非解构renderscreen对象集中了所有查询方法不必每次渲染都维护解构列表。唯一的例外是本例第一个测试——screen没有container属性因此解构render来取得container用于快照断言。模拟点击后再断言状态变化第二个测试先render(App /)再用screen.getByRole(button, { name: Click Me })精确定位按钮await user.click(button)触发点击最后断言标题文本变为radical rhinos。每个测试都要重新renderReact Testing Library 会在每个测试结束后自动卸载已渲染的组件因此必须为每个测试单独渲染。当某个组件的测试较多时beforeEachJest 生命周期钩子可以派上用场。userEvent v14 的异步 API 迁移注意第二个测试的回调函数是async的原因在于user.click()需要await。课程原文档明确说明自 user-event 14.0.0 起user-event API 已更新为异步以模拟真实用户交互的异步本质。而一些旧教程或资料可能仍在演示同步写法// This is the old approach of using userEvent. it(renders radical rhinos after button click, () { render(App /); const button screen.getByRole(button, { name: Click Me }); userEvent.click(button); expect(screen.getByRole(heading).textContent).toMatch(/radical rhinos/i); });这种同步写法依然受支持setup()在内部被隐式触发目的是平滑 v13 到 v14 的迁移。但在新代码中应始终使用userEvent.setup()await的异步模式。快照测试原理、优点与陷阱快照文件长什么样第二个测试运行后Jest 会自动生成一个关联的快照文件App.test.js.snap内容如下// App.test.js.snap // Jest Snapshot v1 exports[magnificent monkeys render 1] div button typebutton Click Me /button h1 Magnificent Monkeys /h1 /div ;快照本质上是组件渲染结果的 HTML 表示。此后每次运行该断言Jest 都会把当前渲染结果与快照文件比对只要App发生哪怕一丁点变化测试就会失败。在新版课程的 Vitest 语境下快照头部则显示为Vitest Snapshot v1见 react/react_testing/introduction_to_react_testing.md原理完全一致。快照测试的优点课程原文档列出了快照的两大优势快且易写一条toMatchSnapshot断言就替代了多行断言代码。例如上面的例子中我们不需要分别断言按钮存在、标题存在——一次快照比对全部覆盖。阻止意外变更悄悄溜进代码任何未被察觉的渲染结构变化都会立即让测试失败起到变更哨兵的作用。快照测试的缺点课程原文档用两个关键词概括了快照的局限误报false positives快照通过并不能证明组件正确——它只证明渲染结果和上次一致。一个真实存在的 bug 可能因为被冻结在快照里而长期逃过检测。过度依赖快照会让开发者对代码产生超出实际的安全感。假阴性false negatives快照对任何细微改动都过于敏感——修正一个标点符号会失败把一个 HTML 标签换成更具语义的标签也会失败。长此以往开发者可能对整个测试套件失去信任。结论是快照本身并不坏它自有用途关键在于理解何时该快照、何时不该快照。合理的做法是把快照用于防止意外回归的稳定区域而把对业务正确性的验证交给显式的行为断言。从测试组织到进阶方向保持每个测试独立、可读课程原文档提醒在组件有大量测试时beforeEach可以避免重复的渲染代码。第二部分 archive/javascript/react_js/react_testing_part_two.md 进一步补充了组织建议尽量把测试的全部准备步骤放在同一个it块内除非测试文件很长、准备代码多达几十行这消除了为了理解某个测试而在整个文件里搜索上下文的负担也让后续审查变更更容易同时降低测试之间状态泄漏的概率。此外应在渲染组件之前调用userEvent.setup()并避免在beforeEach中调用渲染或 userEvent 函数。学习清单课程原文档在 Assignment 一节给出了值得动手完成的练习通读 React Testing Library 的查询方法速查表掌握一个查询场景对应一个最合适的查询方法当内置方法都无法满足时再考虑data-testid。阅读 userEvent 的 API 文档熟悉用户模拟的完整能力。深入阅读关于 Jest 快照测试优缺点、以及快照测试在编程中一般性利弊的讨论文章。后续的进阶内容回调函数与子组件的 Mock、actAPI、Arrange-Act-Assert 模式则可在 archive/javascript/react_js/react_testing_part_two.md 中继续学习仓库也提供了对应的新版课程 react/react_testing/mocking_callbacks_and_components.md。知识自检测试 React 应用需要安装哪些包分别承担什么职责回顾上文测试一个 React 应用需要哪些包一节user-event包的意义是什么为什么它优于fireEventrender方法做了什么返回什么查询元素时最受推荐的方法是什么如何用userEvent测试一次点击事件快照测试的优点是什么缺点又是什么总结围绕课程原文档 archive/javascript/react_js/react_testing_part_one.md 的核心脉络我们完整走通了 React UI 测试的入门路径识别必需的依赖包testing-library/react、testing-library/jest-dom、testing-library/user-event、用renderscreen完成第一次查询、以ByRole作为高优先级查询策略、用异步userEvent模拟真实点击以及辩证地看待快照测试的价值与风险。这些能力与仓库新版课程 react/react_testing/introduction_to_react_testing.md 中的 Vitest 方案一一对应——无论你选择 Jest 还是 Vitest核心的测试哲学查询用户可见的 UI、模拟真实交互、对变更保持警觉始终一致。把它应用到你的下一个组件上你写的每一行测试都会让应用更可维护、更值得信赖。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表