Jest 入门指南:让 JavaScript 测试变得简单高效


在现代前端开发中,测试不再是可有可无的选项,而是保证代码质量的底线。而在 JavaScript 的测试领域,Jest 凭借其“开箱即用”的哲学,成为了当之无愧的行业标准。

由 Facebook 开发的 Jest,不仅适用于 React,也完美支持 Vue、Angular、Node.js 甚至纯 JavaScript 项目。


1. 为什么选择 Jest?

  1. 开箱即用 (Zero Config):内置了断言库、Mock 工具、测试运行器和代码覆盖率报告,不需要像以前那样组合 Mocha + Chai + Sinon。
  2. 极速性能:支持并行执行测试用例,并优先运行上次失败的测试,显著缩短 CI 等待时间。
  3. 快照测试 (Snapshot Testing):一种独特的 UI 测试方式,能够捕捉复杂对象的变更。
  4. 优秀的文档与社区:几乎你遇到的所有测试难题,在社区里都能找到成熟的方案。

2. 基础语法:断言与匹配器

Jest 的核心是 test 函数和 expect 断言。

test('两个数字相加', () => {
  expect(1 + 2).toBe(3);
});

常用匹配器 (Matchers):

  • 相等性toBe (引用相等), toEqual (对象值相等)。
  • 真值检查toBeNull, toBeDefined, toBeTruthy
  • 数字对比toBeGreaterThan, toBeLessThanOrEqual
  • 字符串/数组toMatch (正则), toContain (包含)。

3. Mock 机制:测试的超能力

在单元测试中,我们经常需要隔离外部依赖(如 API 请求、数据库读写)。Jest 提供了三类强大的 Mock 工具:

3.1 jest.fn()

创建一个模拟函数,用于追踪调用情况(参数是什么?被调用了几次?)。

const mockCallback = jest.fn(x => 42 + x);
mockCallback(0);
expect(mockCallback).toHaveBeenCalledTimes(1);
expect(mockCallback).toHaveBeenCalledWith(0);

3.2 jest.spyOn()

监听某个对象的方法,而不改变其原始行为(或者部分替换它)。常用于监控 console.loglocalStorage

3.3 jest.mock()

直接 Mock 整个模块。这在测试组件请求 API 时非常有用:

import axios from 'axios';
jest.mock('axios');

test('应该获取用户数据', async () => {
  axios.get.mockResolvedValue({ data: { name: 'Gemini' } });
  // 执行调用逻辑...
});

4. 企业级实战:NestJS 与 Jest 的集成

NestJS 深度集成了 Jest,并提供了一套专门的测试工具包 @nestjs/testing

4.1 模拟模块 (Test.createTestingModule)

在 NestJS 中,我们不需要手动 new 类,而是使用官方提供的测试模块创建器:

import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';

describe('UsersService', () => {
  let service: UsersService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  it('应该已定义', () => {
    expect(service).toBeDefined();
  });
});

4.2 轻松 Mock 依赖项 (overrideProvider)

当你测试 Controller 时,通常不想让它真的去调数据库,这时可以使用覆盖机制:

const module = await Test.createTestingModule({
  controllers: [UsersController],
  providers: [UsersService],
})
  .overrideProvider(UsersService)
  .useValue({ findAll: jest.fn().mockReturnValue([{ name: 'Mock User' }]) })
  .compile();

4.3 端到端 (E2E) 测试

NestJS 利用 supertest 让我们能模拟真实的 HTTP 请求:

import * as request from 'supertest';

it('/GET users', () => {
  return request(app.getHttpServer())
    .get('/users')
    .expect(200)
    .expect([{ id: 1, name: 'Gemini' }]);
});

5. 快照测试 (Snapshot Testing)

当你想确保 UI 结构或大型配置对象没有意外更改时,快照测试非常高效。

test('快照测试示例', () => {
  const tree = { foo: 'bar', date: new Date() };
  expect(tree).toMatchSnapshot();
});

执行后,Jest 会生成一个 .snap 文件。下次运行时,如果对象结构发生变化,Jest 会报错并询问你是否需要更新快照。


6. 测试的最佳实践

6.1 遵循 AAA 模式

  • Arrange (准备):设置数据、Mock、环境变量。
  • Act (执行):调用被测函数或触发交互。
  • Assert (断言):验证结果是否符合预期。

6.2 保持测试独立性

使用 beforeEachafterEach 来重置状态,确保每个测试用例之间没有干扰。

6.3 关注代码覆盖率

通过 --coverage 参数,Jest 可以生成详细的报告。虽然不追求 100% 的覆盖率,但关键业务逻辑必须被覆盖。


总结

Jest 将测试从一种“负担”变成了一种“享受”。它强大的工具链让你能够专注于逻辑验证,而不是配置环境。

记住:测试不是为了证明你是对的,而是为了在你犯错时及时提醒你。

现在就开始为你的项目加上第一个 expect 吧!