Jest 核心概念
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([]);
});
作用域与顺序
默认 before 和 after 作用于当前文件的每个测试。可以使用 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:每个结果对象包含value或error属性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() 将快照直接写入测试代码,适合小片段。
全局测试工具
test 和 it 等价,it 旨在使测试读起来像句子。使用 describe 进行分组,嵌套 describe 形成结构。
若暂时不想运行某测试,使用 test.skip 或 xit、xdescribe。若要运行唯一测试,使用 test.only 或 fit、fdescribe。
参数化测试:test.each 用表格驱动不同数据,避免重复代码。
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('加法 %i + %i 等于 %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});