你改了一行代码手动点了一遍页面觉得没问题就上线了。结果用户反馈“登录按钮点不动了”。你心里咯噔我根本没改登录相关代码啊。今天我们来给你的代码装一把“智能门锁”——单元测试。用 Jest Testing Library把常见 Bug 锁在门外让你改代码时不再心惊胆战。前言很多前端对测试的态度是项目那么赶哪有时间写测试结果修 Bug 的时间比写代码还多。你花 20 分钟写的测试可能帮你省掉 2 小时的通宵排查。测试不是“额外工作”而是安全网。当你需要重构、升级依赖、添加新功能时测试全绿的那一刻比中彩票还安心。今天我们用 Jest测试框架 Testing Library渲染组件、模拟用户操作从零开始给你的 React 项目写第一个测试。不搞复杂概念只写最实用的断言。一、Jest 是啥Testing Library 又是啥JestFacebook 出的测试框架内置断言、模拟函数、覆盖率报告。开箱即用零配置。Testing Library一套帮助你“像用户一样测试”的工具。不测试组件内部 state 或 props只测试用户能看到和能操作的。核心原则测试越接近用户的使用方式越能给你信心。不要测试实现细节比如某个函数被调用了几次、某个 state 变了要测试 UI 上出现了什么、点击后发生了什么变化。二、环境搭建Create React App 用户如果你用 CRAJest 和 Testing Library 已经内置直接写就行。Vite 用户需要手动安装npminstall-Djest testing-library/react testing-library/jest-dom testing-library/user-event vitest# 如果用 VitestVite 推荐配置略不同。这里我们用 Jest 示范配置jest.config.jsmodule.exports{testEnvironment:jsdom,setupFilesAfterEnv:[rootDir/src/setupTests.js],};src/setupTests.jsimporttesting-library/jest-dom;三、第一个测试测试一个纯函数测试最简单的工具函数是入门的绝佳方式。比如utils/formatPrice.jsexportfunctionformatPrice(price,currency¥){return${currency}${price.toFixed(2)};}写测试utils/formatPrice.test.jsimport{formatPrice}from./formatPrice;test(格式化价格带默认货币符号,(){expect(formatPrice(10.5)).toBe(¥10.50);});test(支持自定义货币符号,(){expect(formatPrice(10.5,$)).toBe($10.50);});运行npm test看到绿色通过。这类测试跑得快你应该写很多。四、测试 React 组件渲染与交互假设我们有一个Counter组件import { useState } from react; export function Counter() { const [count, setCount] useState(0); return ( div p计数: {count}/p button onClick{() setCount(count 1)}增加/button /div ); }写测试Counter.test.jsximport { render, screen } from testing-library/react; import userEvent from testing-library/user-event; import { Counter } from ./Counter; test(渲染初始计数为0, () { render(Counter /); const countElement screen.getByText(/计数: 0/i); expect(countElement).toBeInTheDocument(); }); test(点击按钮后计数增加, async () { const user userEvent.setup(); render(Counter /); const button screen.getByRole(button, { name: /增加/i }); await user.click(button); expect(screen.getByText(/计数: 1/i)).toBeInTheDocument(); });注意screen.getByRole比getByText更语义化推荐优先使用。userEvent模拟真实点击会触发 focus、blur 等比fireEvent更接近用户。五、测试异步操作比如数据加载一个显示用户列表的组件从 API 获取数据import { useEffect, useState } from react; export function UserList() { const [users, setUsers] useState([]); useEffect(() { fetch(/api/users) .then(res res.json()) .then(setUsers); }, []); return ( ul {users.map(user li key{user.id}{user.name}/li)} /ul ); }测试时需要 mockfetchimport { render, screen, waitFor } from testing-library/react; import { UserList } from ./UserList; global.fetch jest.fn(() Promise.resolve({ json: () Promise.resolve([{ id: 1, name: 张三 }, { id: 2, name: 李四 }]), }) ); test(加载并显示用户列表, async () { render(UserList /); // 等待数据加载完成 await waitFor(() { expect(screen.getByText(张三)).toBeInTheDocument(); expect(screen.getByText(李四)).toBeInTheDocument(); }); });六、覆盖率别盲目追求 100%运行npm test -- --coverage会生成覆盖率报告。但记住100% 覆盖率不代表没有 Bug。覆盖率低的地方可能是关键逻辑需要补测试但有些样板代码如常量定义、简单 getter不测也罢。重点覆盖业务逻辑和复杂交互。七、测试最佳实践测试行为不测试实现不要测试组件内部 state 的值除非必要而是测试渲染结果。一个测试只断言一件事一个test里可以有多个expect但最好只测一个行为。模拟外部依赖网络请求、localStorage、计时器都要模拟避免测试不稳定。避免测试快照快照测试toMatchSnapshot容易产生大而脆弱的文件改个空格就挂。优先用断言。让测试快速单元测试应该在几秒内跑完如果慢检查是否有真实网络请求或大量渲染。八、持续集成让测试自动跑起来把测试放到 GitHub Actions 里上篇文章的内容。每次 PR 自动跑测试不通过不让合并。这样团队协作时队友的改动不会悄悄破坏你的代码。name:Teston:[push,pull_request]jobs:test:runs-on:ubuntu-lateststeps:-uses:actions/checkoutv4-uses:actions/setup-nodev4with:node-version:18-run:npm ci-run:npm test九、总结测试是给未来的自己写信写测试一开始会慢但能让你后期“闭着眼睛改代码”。Jest Testing Library 是 React 社区标准Vue/Vite 对应 Vitest Testing Library。不要被“测试种类太多”吓到从纯函数和简单组件开始逐步扩大覆盖。下次你改了代码测试全绿你就可以自信地 push。那种感觉比手动点一百遍页面踏实多了。如果你觉得今天的“智能门锁”够踏实点个赞让更多人看到。评论区聊聊你被上线后突然出现的 Bug 坑过吗