你写的代码没有测试,就像出门不锁门——Jest + Testing Library 从入门到不慌

作者:kyriewen日期:2026/5/11

你改了一行代码,手动点了一遍页面,觉得没问题就上线了。结果用户反馈“登录按钮点不动了”。你心里咯噔:我根本没改登录相关代码啊。今天我们来给你的代码装一把“智能门锁”——单元测试。用 Jest + Testing Library,把常见 Bug 锁在门外,让你改代码时不再心惊胆战。

前言

很多前端对测试的态度是:项目那么赶,哪有时间写测试?结果修 Bug 的时间比写代码还多。你花 20 分钟写的测试,可能帮你省掉 2 小时的通宵排查。

测试不是“额外工作”,而是安全网。当你需要重构、升级依赖、添加新功能时,测试全绿的那一刻,比中彩票还安心。今天我们用 Jest(测试框架)+ Testing Library(渲染组件、模拟用户操作),从零开始给你的 React 项目写第一个测试。不搞复杂概念,只写最实用的断言。

一、Jest 是啥?Testing Library 又是啥?

  • Jest:Facebook 出的测试框架,内置断言、模拟函数、覆盖率报告。开箱即用,零配置。
  • Testing Library:一套帮助你“像用户一样测试”的工具。不测试组件内部 state 或 props,只测试用户能看到和能操作的。

核心原则:测试越接近用户的使用方式,越能给你信心。不要测试实现细节(比如某个函数被调用了几次、某个 state 变了),要测试 UI 上出现了什么、点击后发生了什么变化。

二、环境搭建(Create React App 用户)

如果你用 CRA,Jest 和 Testing Library 已经内置,直接写就行。Vite 用户需要手动安装:

1npm install -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest
2# 如果用 Vitest(Vite 推荐),配置略不同。这里我们用 Jest 示范
3

配置 jest.config.js

1module.exports = {
2  testEnvironment: 'jsdom',
3  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
4};
5

src/setupTests.js

1import '@testing-library/jest-dom';
2

三、第一个测试:测试一个纯函数

测试最简单的工具函数,是入门的绝佳方式。比如 utils/formatPrice.js

1export function formatPrice(price, currency = '¥') {
2  return `${currency}${price.toFixed(2)}`;
3}
4

写测试 utils/formatPrice.test.js

1import { formatPrice } from './formatPrice';
2
3test('格式化价格带默认货币符号', () => {
4  expect(formatPrice(10.5)).toBe('¥10.50');
5});
6
7test('支持自定义货币符号', () => {
8  expect(formatPrice(10.5, '$')).toBe('$10.50');
9});
10

运行 npm test,看到绿色通过。这类测试跑得快,你应该写很多。

四、测试 React 组件:渲染与交互

假设我们有一个 Counter 组件:

1import { useState } from 'react';
2
3export function Counter() {
4  const [count, setCount] = useState(0);
5  return (
6    <div>
7      <p>计数: {count}</p>
8      <button onClick={() => setCount(count + 1)}>增加</button>
9    </div>
10  );
11}
12

写测试 Counter.test.jsx

1import { render, screen } from '@testing-library/react';
2import userEvent from '@testing-library/user-event';
3import { Counter } from './Counter';
4
5test('渲染初始计数为0', () => {
6  render(<Counter />);
7  const countElement = screen.getByText(/计数: 0/i);
8  expect(countElement).toBeInTheDocument();
9});
10
11test('点击按钮后计数增加', async () => {
12  const user = userEvent.setup();
13  render(<Counter />);
14  const button = screen.getByRole('button', { name: /增加/i });
15  await user.click(button);
16  expect(screen.getByText(/计数: 1/i)).toBeInTheDocument();
17});
18

注意:

  • screen.getByRolegetByText 更语义化,推荐优先使用。
  • userEvent 模拟真实点击(会触发 focus、blur 等),比 fireEvent 更接近用户。

五、测试异步操作:比如数据加载

一个显示用户列表的组件,从 API 获取数据:

1import { useEffect, useState } from 'react';
2
3export function UserList() {
4  const [users, setUsers] = useState([]);
5  useEffect(() => {
6    fetch('/api/users')
7      .then(res => res.json())
8      .then(setUsers);
9  }, []);
10  return (
11    <ul>
12      {users.map(user => <li key={user.id}>{user.name}</li>)}
13    </ul>
14  );
15}
16

测试时需要 mock fetch

1import { render, screen, waitFor } from '@testing-library/react';
2import { UserList } from './UserList';
3
4global.fetch = jest.fn(() =>
5  Promise.resolve({
6    json: () => Promise.resolve([{ id: 1, name: '张三' }, { id: 2, name: '李四' }]),
7  })
8);
9
10test('加载并显示用户列表', async () => {
11  render(<UserList />);
12  // 等待数据加载完成
13  await waitFor(() => {
14    expect(screen.getByText('张三')).toBeInTheDocument();
15    expect(screen.getByText('李四')).toBeInTheDocument();
16  });
17});
18

六、覆盖率:别盲目追求 100%

运行 npm test -- --coverage,会生成覆盖率报告。但记住:100% 覆盖率不代表没有 Bug。覆盖率低的地方可能是关键逻辑,需要补测试;但有些样板代码(如常量定义、简单 getter)不测也罢。重点覆盖业务逻辑和复杂交互。

七、测试最佳实践

  • 测试行为,不测试实现:不要测试组件内部 state 的值(除非必要),而是测试渲染结果。
  • 一个测试只断言一件事:一个 test 里可以有多个 expect,但最好只测一个行为。
  • 模拟外部依赖:网络请求、localStorage、计时器都要模拟,避免测试不稳定。
  • 避免测试快照:快照测试(toMatchSnapshot)容易产生大而脆弱的文件,改个空格就挂。优先用断言。
  • 让测试快速:单元测试应该在几秒内跑完,如果慢,检查是否有真实网络请求或大量渲染。

八、持续集成:让测试自动跑起来

把测试放到 GitHub Actions 里(上篇文章的内容)。每次 PR 自动跑测试,不通过不让合并。这样团队协作时,队友的改动不会悄悄破坏你的代码。

1name: Test
2on: [push, pull_request]
3jobs:
4  test:
5    runs-on: ubuntu-latest
6    steps:
7      - uses: actions/checkout@v4
8      - uses: actions/setup-node@v4
9        with:
10          node-version: 18
11      - run: npm ci
12      - run: npm test
13

九、总结:测试是给未来的自己写信

  • 写测试一开始会慢,但能让你后期“闭着眼睛改代码”。
  • Jest + Testing Library 是 React 社区标准,Vue/Vite 对应 Vitest + Testing Library。
  • 不要被“测试种类太多”吓到,从纯函数和简单组件开始,逐步扩大覆盖。

下次你改了代码,测试全绿,你就可以自信地 push。那种感觉,比手动点一百遍页面踏实多了。

如果你觉得今天的“智能门锁”够踏实,点个赞让更多人看到。评论区聊聊:你被上线后突然出现的 Bug 坑过吗?


你写的代码没有测试,就像出门不锁门——Jest + Testing Library 从入门到不慌》 是转载文章,点击查看原文


相关推荐


Git Worktree: AI 编程 Agent 并行开发的秘密武器
陈佬昔编程人生2026/5/1

你在 AI 编程工具里开发一个新功能,突然产品过来让修复一个紧急 bug。于是你开了两个 AI Agent: Agent A:在 feature/new-dashboard 上写新功能 Agent B:在 fix/login-bug 上修一个登录 Bug 你心想:"两个 Agent 同时干活,效率翻倍。" 但三分钟后你回到编辑器,看到的是一幅这样的画面: app/ ├── dashboard.tsx ← Agent A 刚改了这里,但没写完 ├── login.tsx ←


我把 Hermes 里的模型几乎测了一遍,得出一个很扎心的结论:越贵的,往往越强
孟健AI编程2026/4/23

大家好,我是孟健。 这几周我在 Hermes 里来回切了很多模型。真跑下来,我越来越确认一件事:模型的水平,很多时候早就写在价格里了。把性价比榜倒过来看,八九不离十就是质量排行。 这不是 benchmark 结论。 是我把 Hermes 当生产底座,拿它去跑多 Agent、长流程、代码任务、资料整理之后,交出来的体感排序。 01 先给排序:贵,很多时候不是乱贵 先看这张图。 图里是按价格排的:便宜的在前,贵的在后。 但我这轮实际测下来,如果你把它倒过来看,它反而更像质量榜。 我的主观体感


c++从入门到跑路——string类
小肝一下2026/4/14

c++从入门到跑路——string类 1.为什么学习string类? 1.1 C语言中的字符串 C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。 1.2 两个面试题(暂不做讲解) 把字符串转换成整数_牛客题霸_牛客网 415. 字符串相加 - 力扣(LeetCode) 在OJ中,有关字符串的题目基本以stri


火爆全网的Seedance2.0 十万人排队,我2分钟就用上了
AI袋鼠帝2026/4/6

大家好,我是袋鼠帝。 之前我在B站看到一位AI视频创作者分享他的工作流。不可否认,那套流程做出来的视频确实很专业,画面精美,运镜流畅。但是,看完我只觉得头皮发麻。 原文档找不到了,我记得他先是用Gemini写剧本,接着用NanoBanana跑画面,然后再去另外的配音平台搞音频,中间穿插着使用ComfyUI来控制视频、图片生成。 ComfyUI这玩意儿我以前也折腾过几次,连线复杂就算了,每个节点的各种配置参数直接给我整懵逼了,我感觉比当初学敲代码还难,后面就再也没碰过了。 然后整个流程的最后一步,


Vue项目打包为WAR文件部署Tomcat完整指南
蒙眼过河2026/3/28

Vue项目打包为WAR文件部署Tomcat完整指南 前言 在Vue项目开发完成后,通常我们会将打包后的静态文件部署到Nginx等静态服务器上。但在某些企业环境中,我们需要将Vue项目部署到Tomcat这样的Java应用服务器中。本文将详细介绍如何将Vue项目的打包文件转换为标准的WAR包,以便部署到Tomcat服务器。 为什么需要将Vue打包为WAR包? 企业规范要求:很多企业使用统一的Tomcat应用服务器集群统一管理:便于与后端Java应用统一部署和管理历史遗留系统:部分老系统架构需


Django 基础入门教程(第四篇):Form组件、Auth认证、Cookie/Session与中间件
冉成未来2026/3/20

在前三篇中,我们完成了 Django 的环境搭建、模型设计、视图模板、Admin 后台以及 ORM 高级查询。本篇将带你深入 Django 的用户交互与安全机制:Form 组件、Auth 认证系统、Cookie/Session 和中间件。学完本篇,你将能够处理复杂的表单验证、实现用户注册登录、管理用户会话,并理解 Django 的请求/响应处理流程。 第一部分:Django Form 组件 1.1 为什么需要 Form 组件? 在 Web 开发中,处理表单是常见且复杂的任务。你需要:


AI时代的数据对比:DBA还需要盯着屏幕看差异吗?
NineData2026/3/12

当 AI 已经能写 SQL、辅助诊断、生成代码时,很多企业的数据对比却还停留在相对原始的阶段:任务跑完,DBA 需要面对动辄上百张表的差异报告,逐行核对的工作量极大。 这种场景在迁移、同步、数据备份演练里并不少见,到了国产化迁移场景下更是被进一步放大。数据库从 Oracle 迁到达梦、从 MySQL 迁到人大金仓,变化的不只是运行环境,更是数据库内核、数据类型、字符集规则和兼容语义。DBA 担心的往往不是任务失败,而是任务看起来已经完成,业务流量切换之后才发现数据并不一致。 AI 时代的数据对


ubuntu应用深度守护
字节逆旅2026/3/4

二、 定位分析:抽丝剥茧 1. 系统日志中的“启动死循环” 输入sudo grep "linux-myApp" /var/log/syslog调取 syslog 发现,系统曾多次尝试自动拉起应用,但均告失败。 报错核心:Exec binary ... does not exist: No such file or directory。 结论:系统预设的自动启动路径与实际安装路径不匹配,导致应用在服务器重启后无法“回家”。 2. 定位原因 上面的日志内容意味着我的应用可能已经被卸载、被移动了位


【C++】整数类型(Integer Types)避雷指南与正确使用姿势
PAK向日葵2026/2/24

背景 C++继承自C语言。作为一门以零开销抽象为主要特征的底层语言,不同于Python或JavaScript等高抽象层次的语言,C++拥有一套较为完整、但又包含有一定历史包袱的内建整数类型。 在实际开发中,如果对C++内建整数类型的机制不熟悉,或者不遵循一定的使用规范,则非常容易引入难以排查和调试的Bug。因此学习了解C++中内建整数类型的特性,以及一套行之有效的使用规范,是非常有必要的。 内建整数类型的坑 or 历史包袱 C++ 标准没有规定具体位数 虽然在实际实践中,我们知道在x64平台,对


百度 APP 正式接入 OpenClaw,所有人限时免费!
苍何2026/2/15

这是苍何的第 495 篇原创! 大家好,我是苍何。 最近被 OpenClaw 刷屏了吧? 3 周时间 GitHub Star 干到 19 万,比当年 DeepSeek 还猛。 我也发了好几篇文章了,然后还开源了个知识库,你别说,还挺多人用的。 基本上接入 QQ、微信、飞书、discord 等都写的比较全了。 但是说实话,OpenClaw 的部署使用过程并不算丝滑。 买服务器、配环境、装依赖,光是部署就需要折腾大半天。 好不容易跑起来了,还得通过 Telegram 来发指令。 就,怎么说呢,能用

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读