资讯详情

资讯详情

React 组件测试进阶:用 Mock 测试回调处理器与子组件(Odin Project 课程实战)

React 组件测试进阶用 Mock 测试回调处理器与子组件Odin Project 课程实战【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本篇技术指南基于开源仓库GitHub_Trending/cu/curriculumThe Odin Project 开放 Web 开发课程中的 mocking_callbacks_and_components.md 展开系统讲解 React 组件测试中的 Mock 技术如何用vi.fn()模拟回调 props、如何用模块 Mock 替换子组件并结合 The Odin Project 网站真实组件SubmissionsList的测试用例带你掌握可落地的组件测试实战能力。读完本篇你将能够独立为带回调 props 的组件编写高质量单元测试并理解大规模组件树下的子组件 Mock 策略与 Arrange-Act-Assert 测试组织模式。一、为什么需要 Mock从纯函数到组件测试在进入 React 测试之前课程体系已经通过 testing_basics.md 与 more_testing.md 建立了 TDD 与 Mock 的基础认知测试隔离一次只测一个方法测试不应依赖外部函数正确工作否则失败时难以快速定位原因紧耦合代码难以测试函数内部引用其他函数、DOM 方法或console.log时几乎无法独立验证其逻辑两个解决方案首选把依赖从代码中解耦出来写成纯函数当无法解耦时就使用Mock——编写一个行为完全受控的假函数例如为从 DOM 输入获取信息的函数创建一个始终返回固定值的假版本从而避免在测试中搭建真实页面。在 React 组件测试中Mock 的价值进一步放大组件通过 props 接收回调、渲染子组件这些依赖大多无法也不应该在测试中真实执行。正如 mocking_callbacks_and_components.md 开头所述Mock 的概念在更早的 JavaScript 测试章节如 Battleship 项目见 project_battleship.md中已经引入本篇将聚焦它在 React 测试中的两种典型形态Mock 回调处理器与Mock 子组件。从源码结构看本仓库在 react/react_testing/ 目录下由两篇姊妹课程组成先由 introduction_to_react_testing.md 完成环境搭建与基础查询/事件模拟再由本文深入 Mock 技术二者构成完整的 React 测试学习链路。二、测试回调处理器Callback Handlers2.1 待测组件一个极简按钮回调无处不在——每一次用户交互都涉及回调它们常作为 props 传入用于改变父组件的状态。考虑如下按钮组件// CustomButton.jsx const CustomButton ({ onClick }) { return ( button onClick{onClick}Click me/button ); }; export default CustomButton;这个组件并不复杂它接收一个onClickprop。我们不知道这个函数内部做了什么也不知道它会如何影响应用唯一确定的是用户点击按钮时它必须被调用。这正是 Mock 的用武之地——用vi.fn()造一个占位函数只验证调用行为不关心真实逻辑。2.2 完整测试代码逐行拆解// CustomButton.test.jsx import { vi, describe, it, expect } from vitest import { render, screen } from testing-library/react; import userEvent from testing-library/user-event; import CustomButton from ./CustomButton; describe(CustomButton, () { it(should render a button with the text Click me, () { render(CustomButton onClick{() {}} /); const button screen.getByRole(button, { name: Click me }); expect(button).toBeInTheDocument(); }); it(should call the onClick function when clicked, async () { const onClick vi.fn(); const user userEvent.setup() render(CustomButton onClick{onClick} /); const button screen.getByRole(button, { name: Click me }); await user.click(button); expect(onClick).toHaveBeenCalled(); }); it(should not call the onClick function when it isnt clicked, () { const onClick vi.fn(); render(CustomButton onClick{onClick} /); expect(onClick).not.toHaveBeenCalled(); }); });三个测试覆盖了该组件的全部要点需要弄清每个函数来自哪个包函数 / API来源包作用describe/it/expectvitest测试框架核心组织套件、定义用例、断言vi.fn()vitest创建 Mock 函数记录调用次数、调用参数rendertesting-library/react将组件渲染进 jsdom 模拟 DOM供后续查询screen.getByRoletesting-library/react按可访问性角色查询元素配name选项可精确匹配userEvent.setup()/user.clicktesting-library/user-event模拟真实用户交互异步 API需awaittoBeInTheDocument/toHaveBeenCalledtesting-library/jest-dom等自定义匹配器断言存在性与调用行为第一个测试验证渲染getByRole(button, { name: Click me })是按角色 可访问名称查询的推荐写法来自 introduction_to_react_testing.md 中强调的ByRole优先原则它同时保证了 UI 的可访问性。第二个测试验证点击调用vi.fn()创建 Mock 函数并作为onClick传入await user.click(button)模拟点击后断言onClick被调用过toHaveBeenCalled。第三个测试验证反向场景组件渲染后未点击断言onClick从未被调用not.toHaveBeenCalled。注意第二个测试的回调函数是async的——因为user.click()返回 Promise必须await。这一异步 API 设计在 introduction_to_react_testing.md 中已有铺垫testing-library/user-event提供userEventAPI 模拟网页交互。2.3 更进一步断言调用次数与参数仅断言被调用往往不够。本仓库归档目录下的早期版本 react_testing_part_two.md 提供了一个输入组件的测试示例展示了 Mock 断言的三层进阶能力const onChangeMock vi.fn(); // 旧版为 jest.fn() const user userEvent.setup(); render(FavoriteInput onChange{onChangeMock} /); const input screen.getByRole(textbox); await user.type(input, Lion); expect(onChangeMock).toHaveBeenCalledTimes(4); // 调用次数输入 4 个字符触发 4 次 expect(onChangeMock).toHaveBeenNthCalledWith(1, L); // 第 1 次调用参数 expect(onChangeMock).toHaveBeenNthCalledWith(2, Lo); // 第 2 次调用参数toHaveBeenCalledTimes(4)user.type逐字符触发验证回调被调用的精确次数toHaveBeenNthCalledWith(1, L)/(2, Lo)验证第 N 次调用收到的具体参数此处是累计输入的字符串toHaveValue(...)直接断言输入框当前值作为另一种验证方式。这类断言能精确锁定回调与用户输入的对应关系是vi.fn()相比普通空函数的核心优势——Mock 函数自带记忆能力。2.4 实践建议setup 放在哪里原文档给出了两条明确的工程实践建议直接关系到测试的可维护性优先在每个测试块内完成所有 setup而非统一放进beforeEach。理由有三单个测试自包含无需在整个文件中检索上下文便于后续 review 变更减少泄漏mock 状态在用例间串扰导致整个测试套件出问题的概率除非测试文件特别长、测试准备代码长达数十行否则都应默认在每个测试块内 setup只有在这种极端情况下才考虑beforeEach。userEvent.setup()应在渲染组件之前调用并且不推荐在测试体之外例如beforeEach中调用 render 和userEvent函数。如果多个测试重复相同代码推荐的做法是编写一个 setup 函数来缩短每个测试而不是依赖beforeEach。三、Mock 子组件让测试聚焦于被测单元3.1 为什么要 Mock 子组件你可能已经接触过模块 Mock的概念。在 React 中当组件树变大时测试会变得错综复杂——尤其是位于树高层的组件渲染它会连带渲染整棵子树任何一层子组件的副作用、异步请求或外部依赖都会干扰断言。此时 Mock 子组件能让测试聚焦于被测组件自身的逻辑。需要说明的是这不是一个日常高频操作原文档也强调 This is not something youll come across often但当项目中出现多层嵌套组件、第三方渲染库或昂贵的子组件时它是值得掌握的关键手段。3.2 真实案例Mock 掉Submission组件原文档以 The Odin Project 官网的SubmissionsList项目提交列表组件为例其测试中 Mock 子组件的写法如下jest.mock(../submission, () ({ submission, isDashboardView }) ( div contenteditable="false">【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →