Jest 核心概念

FreeGuideOnline 最新 2026-07-16

javascript test('基础匹配', () => { expect(2 + 2).toBe(4); expect({ name: 'Jest' }).toEqual({ name: 'Jest' }); });


- **`toBe`**:使用 `Object.is` 进行精确相等比较,适用于基本类型。
- **`toEqual`**:递归检查对象或数组的每个字段,适用于引用类型。
- **`not`**:可链式调用以反转任何匹配器,例如 `expect(value).not.toBe(0)`。

### 真值判断

区分 `undefined`、`null`、`false` 等假值非常重要:

- `toBeNull()` – 只匹配 `null`
- `toBeUndefined()` – 只匹配 `undefined`
- `toBeDefined()` – 与 `toBeUndefined` 相反
- `toBeTruthy()` – 匹配任何 `if` 视为真的值
- `toBeFalsy()` – 匹配任何 `if` 视为假的值

```javascript
test('真值检查', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

数字比较

大多数数字比较都有对应的匹配器:

  • toBeGreaterThan()
  • toBeGreaterThanOrEqual()
  • toBeLessThan()
  • toBeLessThanOrEqual()

对于浮点数,使用 toBeCloseTo 避免舍入误差。

字符串

使用 toMatch 检查字符串是否匹配正则表达式:

test('字符串匹配', () => {
  expect('team').not.toMatch(/I/);
  expect('Christoph').toMatch(/stop/);
});

数组与可迭代对象

通过 toContain 检查数组或可迭代对象是否包含特定项。

异常

测试函数是否抛出异常时,将函数包装在箭头函数内并调用 toThrow

test('抛出错误', () => {
  const throwError = () => {
    throw new Error('出错了');
  };
  expect(throwError).toThrow();
  expect(throwError).toThrow(Error);
  expect(throwError).toThrow('出错了');
  expect(throwError).toThrow(/错/);
});

异步代码测试

Jest 原生支持测试异步操作。有几种模式:

回调函数

若函数接受回调,可使用 done 参数。若 done 未被调用,测试将超时失败。

test('回调测试', done => {
  function callback(data) {
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }
  fetchData(callback);
});

Promise

返回一个 promise,Jest 会等待其完成。若 promise 被 reject,测试失败:

test('promise 成功', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

若要断言 promise 被拒绝,使用 expect.assertions 验证调用次数,并配合 .catch.rejects

test('promise 失败', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});

.resolves / .rejects

直接在 expect 中使用:

test('使用 resolves', () => {
  return expect(fetchData()).resolves.toBe('peanut butter');
});

test('使用 rejects', () => {
  return expect(fetchData()).rejects.toThrow('error');
});

Async/Await

最简洁的方式是在测试函数前加上 async,并在内部使用 await

test('async/await 成功', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('async/await 失败', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

或结合 .resolves / .rejects

test('组合 async 和 resolves', async () => {
  await expect(fetchData()).resolves.toBe('peanut butter');
});

设置与拆卸 (Setup and Teardown)

在测试前后执行一些初始化和清理操作,使用以下生命周期函数:

  • beforeAll / afterAll:在所有测试前/后执行一次(适用于数据库连接等昂贵操作)。
  • beforeEach / afterEach:每个测试前/后都执行,保证测试隔离。
let database;

beforeAll(() => {
  database = connect();
});

afterAll(() => {
  database.disconnect();
});

beforeEach(() => {
  database.reset();
});

test('测试1', () => {
  expect(database.find()).toEqual([]);
});

作用域与顺序

默认 beforeafter 作用于当前文件的每个测试。可以使用 describe 块将测试分组,此时生命周期函数仅在块内生效:

describe('子模块A', () => {
  beforeEach(() => {
    // 只对该 describe 内的测试生效
  });

  test('A1', () => { /* ... */ });
  test('A2', () => { /* ... */ });
});

Jest 先执行所有 describe 处理程序,然后按顺序运行测试,因此在 describe 内声明的变量在执行测试前就已初始化。

模拟函数 (Mock Functions)

模拟函数用于擦除真实的实现、捕获调用信息、注入返回值或完整实现。创建方式:

const myMock = jest.fn();

模拟函数支持链式设置返回值:

myMock
  .mockReturnValueOnce(10)
  .mockReturnValueOnce('x')
  .mockReturnValue(true);

检测模拟函数调用

模拟函数记录了所有调用:

  • myMock.mock.calls:每层数组代表一次调用的参数列表
  • myMock.mock.results:每个结果对象包含 valueerror 属性
  • myMock.mock.instances:若函数被 new 调用,存储实例对象

利用这些属性编写断言:

expect(myMock).toHaveBeenCalled();
expect(myMock).toHaveBeenCalledTimes(2);
expect(myMock).toHaveBeenCalledWith(arg1, arg2);
expect(myMock).toHaveBeenLastCalledWith(arg1);

模拟模块

当需要模拟整个模块,比如 axios,可以在测试文件同级创建 __mocks__ 文件夹,写入手动模拟:

// __mocks__/axios.js
module.exports = {
  get: jest.fn(() => Promise.resolve({ data: { user: 'Bob' } }))
};

在测试中调用 jest.mock('模块路径'),Jest 将自动替换为模拟版本。也可不编写手动模拟,直接内联工厂函数:

jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve({ data: {} }))
}));

模拟函数实现

可以为模拟函数提供完整实现:

jest.fn(implementation);
mockFn.mockImplementation(fn);
// 或仅一次
mockFn.mockImplementationOnce(fn);

模拟返回 this 可以进行链式调用模拟。

监视对象方法

不想完全替换整个模块,只是监视对象上的方法,使用 jest.spyOn

const video = {
  play: () => 'real'
};
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
spy.mockRestore(); // 恢复原始实现

快照测试 (Snapshot Testing)

快照测试可以确保 UI 不会意外改变。典型的用途是测试 React 组件的渲染输出,但也适用于任何可序列化的值。

test('渲染结果快照', () => {
  const tree = renderer.create(<Link page="http://example.com">Example</Link>).toJSON();
  expect(tree).toMatchSnapshot();
});

首次运行会在 __snapshots__ 文件夹生成快照文件。后续运行时将生成的输出与存储的快照比较,不匹配则测试失败。

更新快照:手动按 u 键或运行 jest --updateSnapshot

快照只应纳入版本控制,并在代码审查时认真对待。对于动态数据,例如日期、ID,可以使用属性匹配器:

expect(user).toMatchSnapshot({
  id: expect.any(Number),
  createdAt: expect.any(Date),
});

内联快照

使用 toMatchInlineSnapshot() 将快照直接写入测试代码,适合小片段。

全局测试工具

testit 等价,it 旨在使测试读起来像句子。使用 describe 进行分组,嵌套 describe 形成结构。

若暂时不想运行某测试,使用 test.skipxitxdescribe。若要运行唯一测试,使用 test.onlyfitfdescribe

参数化测试:test.each 用表格驱动不同数据,避免重复代码。

test.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])('加法 %i + %i 等于 %i', (a, b, expected) => {
  expect(a + b).toBe(expected);
});