人人都会AI编程

23.4 单元测试:Jest / Vitest 测试体系

更新时间:2026-07-11

代码写得再漂亮,没有测试的保护,每一次修改都像是在悬崖边行走。单元测试的价值不在于“证明代码没有问题”,而在于当代码出问题时你能第一时间知道,并且能精准定位。对 JavaScript 项目来说,Jest 和 Vitest 是目前最主流的两套测试框架,它们都提供了零配置起步、断言库内置、Mock 功能齐备的完整测试体验。

为什么需要单元测试

在实际项目中有几个非常真实的场景:

  • 你修改了一个工具函数,但不知道有没有影响其他调用方。
  • 你重构了某个模块的内部实现,想确认外部行为没有变化。
  • 你接手了一个没有文档的老项目,想知道某个函数在各种输入下会输出什么。
  • 你和同事并行开发,他的改动不小心破坏了你的逻辑。

单元测试就是针对这些问题的最轻量保险——用代码验证代码的正确性。它虽然不能替代集成测试和端到端测试,但写在最底层的单元测试运行速度最快,反馈最及时,是测试金字塔的坚实基座。

Jest:久经考验的业界标准

Jest 由 Facebook 开源,是 React 官方推荐的测试框架,也广泛用于 Vue、Node.js 等各类项目。它的最大特点是 开箱即用——安装后几乎不需要配置就能运行测试。

快速上手

npm install --save-dev jest

package.json 中添加脚本:

{
  "scripts": {
    "test": "jest"
  }
}

然后编写一个测试文件 math.test.js

// 被测函数
function sum(a, b) {
  return a + b;
}

// 测试用例
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

运行 npm test,Jest 会自动找到所有 .test.js.spec.js 文件,执行里面的测试用例,并输出清晰的结果报告。

Jest 的核心能力

  • 内置断言库expect(value).toBe(expected).toEqual().toContain().toThrow() 等,语义清晰。
  • Mock 功能强大jest.fn() 创建模拟函数,jest.mock() 可以模拟整个模块,非常适合隔离外部依赖。
  • 快照测试expect(component).toMatchSnapshot() 用于 UI 组件的回归测试,检测渲染输出是否意外改变。
  • 覆盖率报告jest --coverage 一键生成语句、分支、函数、行覆盖率报告。
  • 并行执行:每个测试文件在独立的进程中并行运行,充分利用多核,提高执行速度。

一个更贴近实战的例子

// userService.js
import axios from 'axios';

export async function getUser(id) {
  const response = await axios.get(`/api/users/${id}`);
  return response.data;
}

// userService.test.js
import axios from 'axios';
import { getUser } from './userService';

jest.mock('axios');

test('getUser returns user data', async () => {
  const mockUser = { id: 1, name: 'Alice' };
  axios.get.mockResolvedValue({ data: mockUser });

  const result = await getUser(1);
  expect(result).toEqual(mockUser);
  expect(axios.get).toHaveBeenCalledWith('/api/users/1');
});

通过 jest.mock 将 axios 替换为模拟模块,测试不再依赖真实网络,快速且稳定。

Vitest:为 Vite 生态而生的新星

Vitest 是伴随 Vite 成长起来的测试框架,它充分利用 Vite 的快速编译能力,为现代前端项目提供极速的测试体验。如果你已经在用 Vite 作为构建工具,Vitest 几乎是无缝的选择。

快速上手

npm install --save-dev vitest

如果项目已经使用 Vite,Vitest 会自动复用 vite.config.js 中的配置(如路径别名、插件等),这意味着测试环境与生产构建环境高度一致,避免了“测试能通过但打包报错”的麻烦。

测试文件的语法与 Jest 高度兼容:

import { describe, it, expect } from 'vitest';

describe('sum function', () => {
  it('adds two numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

Vitest 的独特优势

  • 原生 ESM 支持:不需要将 import/export 转换为 CommonJS,与现代前端项目的模块系统完全一致。
  • HMR 驱动的智能监听模式vitest --watch 可以利用 Vite 的模块热替换,只重新执行受影响的测试,反馈极快。
  • 实验性的浏览器模式:可以直接在真实的浏览器环境中运行测试,验证 DOM 操作和 Web API。
  • 与 Vite 生态深度集成:可以直接使用 Vite 的插件,也能直接运行 .ts.tsx 文件而无需额外配置。

Jest 与 Vitest 的对比选型

两者不是零和博弈,而是面向不同项目阶段的工具。下表列出关键差异:

| 维度 | Jest | Vitest |
|------|------|--------|
| 构建依赖 | 内置 transformer,需要配置 | 复用 Vite,零配置起步 |
| 启动速度 | 大型项目较慢(依赖解析、编译) | 利用 esbuild 预构建,启动极快 |
| ESM 支持 | 需额外配置(实验性支持) | 原生支持 |
| 生态环境 | 极其成熟,文档和第三方资源丰富 | 增长迅速,社区活跃 |
| 学习成本 | 高(配置多,坑点多) | 低(若已熟悉 Vite) |

选型建议

  • 新项目如果计划使用 Vite,Vitest 几乎是首选。
  • 大型遗留项目或 React 项目(特别是使用 Create React App 的)可能已经深度集成了 Jest,迁移有成本,继续使用 Jest 完全没问题。
  • 如果你需要在一套测试中同时覆盖 Node.js 和浏览器环境,Vitest 的浏览器模式可能是一个加分项。
  • API 和断言语法两者几乎相同,从 Jest 切换到 Vitest 几乎没有学习成本。

构建测试体系的实践原则

框架只是工具,测试的质量取决于写测试的习惯。

  • 从工具函数开始:纯函数最容易测试,也是性价比最高的测试切入点。
  • 遵循 AAA 模式:Arrange(准备数据)、Act(执行被测函数)、Assert(断言结果),让测试结构清晰。
  • 一测试一场景:每个测试只验证一种行为,失败了能立刻定位原因。
  • 不追求 100% 覆盖率:覆盖率是参考,不是目标。关键业务逻辑和容易出错的地方要覆盖,简单的 getter/setter 不必逐行测试。
  • 持续运行:把测试脚本挂到 Git hooks 或 CI 流水线中,确保每次提交都通过已有的测试用例。

单元测试不是负担,而是你在代码世界里行走的安全绳。投入一点时间搭建好 Jest 或 Vitest 的测试体系,未来的你和你的同事都会感谢现在这个“提前一步”的决定。