你写的代码没有测试,就像出门不锁门——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 从入门到不慌》 是转载文章,点击查看原文


相关推荐


window管理开发环境篇 - 持续更新
pe7er2026/5/2

我个人非常喜欢那种一键部署开发环境的方式,但时间一长,我们会淡忘如何部署开发环境,它会让我们失去对开发环境的控制。 下面我记录window环境下我是如何管理开发环境的。 安装Scoop 设置前提条件 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 典型安装 irm get.scoop.sh | iex 使用代理安装 iex "& {$(irm get.scoop.sh -Proxy 'http://<ip:


NineData 新增支持 GaussDB 到 StarRocks 实时数据复制能力
NineData2026/4/23

很多企业在完成核心系统国产化之后,业务已经稳定跑在 GaussDB 上,但很快会进入下一阶段:经营分析、实时看板、主题查询、风控报表、数据服务层都需要尽快接上。 如何实现呢?把业务数据实时复制到数仓即可。但通常会有如下挑战: 历史数据需要快速初始化到位。 业务持续写入时,目标端要持续、稳定地追平变化。 任务运行出了问题,要能第一时间感知,如果等到下游发现数据不对那就晚了。 正式上线前,要能全自动化对复制结果做核验,人工抽样费时费力还容易出错。 这个时候,一条能长期稳定的实时数据复制链路就很


告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
GetcharZp2026/4/14

想用 Go 语言开发大模型应用却找不到好用的框架?本文深度解析 LangChainGo,手把手教你快速上手,涵盖 RAG、智能体等核心场景,助你轻松跨入 AI 开发大门! 在人工智能大行其道的今天,提到 LLM(大语言模型)应用开发,很多人脑海中浮现的第一反应就是 Python。确实,Python 拥有得天独厚的生态。但随着 AI 应用进入“工程化”下半场,开发者们开始面临新的挑战:并发性能瓶颈、部署环境复杂、内存消耗大…… 这时候,Go 语言的优势便凸显了出来。其天生的并发处理能力(Gor


**发散创新:基于以太坊侧链的高性能去中心化应用部署实战**在区块链生态中,*
好家伙VCC2026/4/6

发散创新:基于以太坊侧链的高性能去中心化应用部署实战 在区块链生态中,主链性能瓶颈一直是制约大规模 DApp 发展的核心问题。为突破这一限制,8*侧链(Sidechain)技术应运而生**,它通过与主链的安全通信机制,在保证去中心化前提下实现高吞吐量和低延迟交易处理。 本文将以 Solidity + Golang + Polygon SDK 为例,构建一个完整的侧链开发流程,并展示如何将智能合约部署到自定义侧链节点上,同时确保与 Ethereum 主网的状态同步验证。 🔧 一、为什么


彻底搞懂大模型 Temperature、Top-p、Top-k 的区别!
Surmon2026/3/29

调用大模型的时候,总会看到几个耳熟能详的参数:Temperature、Top-p、Top-k。文档里通常的解释都是:控制输出的随机性。也就是:Temperature 和 Top-p 的值越高,模型输出的结果会越随机、越富有创造性,反之,数值越低,输出的结果就越确定、越保守。 随机性,到底是个什么意思?为啥随机性就可以表现为创造性? 回答这个问题,得先从一个最朴素的问题开始:模型是如何回答问题的。 我之前在 《从统计学习到通用智能》 中曾经提到过,大模型在输出文本的时候,本质上是在 滚动地预测下


当我开始像写代码一样和AI对话,一切都变了
lbh2026/3/21

当AI成为你的开发伙伴,如何让它真正听懂你的需求? 身为一名前端开发。在日常开发中,我经常和AI打交道——用它写代码、调试bug、优化性能、设计方案。但说实话,很长一段时间里,我和AI的对话就像这样: 我:“帮我写一个响应式导航栏。” AI:(给出一个基础版) 我:“不是这样的,要带下拉菜单。” AI:(加上下拉菜单) 我:“还要在移动端变成汉堡菜单。” AI:(加上汉堡菜单) 我:“……能不能一次说完?” 你是不是也有类似的经历? 后来我慢慢发现,问题不在AI,而在我自己。就像写代码一样


使用Fetch API 探索前后端数据交互
独泪了无痕2026/3/13

前言   在当今的 Web 开发中,前端与后端的数据交互是构建动态应用的核心。API 是连接不同软件应用的重要桥梁,允许开发者通过 HTTP 请求与服务器交互,高效调用API数据对于构建现代 Web 应用至关重要。传统的页面刷新方式已经无法满足用户对流畅体验的需求,而 Fetch API 的出现为 JavaScript 带来了全新的生命力。 一、Fetch API 概述 1.1 Fetch API 是什么❓   Fetch API 是现代浏览器提供的一个用于发起网络请求的接口,用于发起 HTTP


C# 可变引用类型和不可变引用类型
CnLg.NJ2026/3/4

引用类型(class)的实例存储在托管堆上,变量保存的是对象的引用。根据对象创建后其状态是否允许被修改,可以将引用类型分为可变(Mutable)和不可变(Immutable)两类。 1. 可变引用类型 定义:对象创建后,其内部状态(字段、属性)可以被修改。 特点: 可以通过公开的 setter 或方法更改属性值。 同一对象在不同时间点可能呈现不同状态。 多线程环境下需要同步机制保证线程安全。 容易产生副作用,因为多个引用可能指向同一对象,一处修改会影响所有引用。


【HarmonyOS】day37:React Native实战项目+关键词高亮搜索Hook
星空22232026/2/24

【HarmonyOS】React Native实战项目+关键词高亮搜索Hook 📅 更新时间:2026年2月 🎯 技术栈:HarmonyOS NEXT + React Native 0.72.5 + TypeScript ⏱️ 阅读时间:约15分钟 前言 进入2026年,移动端开发格局已发生根本性变化。随着HarmonyOS NEXT彻底剥离AOSP,开发者面临着Android、iOS、HarmonyOS三足鼎立的局面。如何用一套代码高效覆盖三大平台? 本文将带你从零开


超详细的云服务部署 OpenClaw 并接入飞书全流程,别再趟坑了
vortesnail2026/2/16

先讲点题外话 大概是 2015 年,我在大学寝室看了一部电影《她》,讲的是一个人与人工智能相爱的科幻爱情电影。 电影中的“女主”是斯嘉丽配音的人工智能操作系统 OS1 ,她可以深入了解、分析并理解你的生活,通过每日的经历不断成长和完善,不仅能够帮你实打实干事,还能够理解环境和用户的情绪,从而不断地进化成一个你越来越信任和依赖的伙伴。 那时候觉得这种形态的产品终究是会来的,想着 50 岁左右应该人工智能能发展到这种程度,但 2026 年的今天,已经能看到这种产品的雏形了!并且这回我坚定相信,今年

首页编辑器站点地图

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

Copyright © 2026 XYZ博客