Jest
Jest 是一个流行的 JavaScript 测试框架,用于编写和运行测试。它由 Facebook 开发,并被应用于测试 React 组件以及其他类型的 JavaScript 代码。Jest 被设计为零配置,易于上手,同时提供了丰富的特性,如快照测试、内置的覆盖率报告和模拟系统。

查看更多相关内容
Jest 中有哪些测试匹配器(Matchers)?如何使用自定义匹配器?Jest 提供了多种测试匹配器(Matchers)来验证不同的条件:
**相等性匹配器:**
- `toBe(value)`:严格相等(`===`)
- `toEqual(value)`:深度相等
- `toStrictEqual(value)`:严格深度相等(包括 undefined 属性)
- `toMatchObject(object)`:部分匹配对象
**真值匹配器:**
- `toBeNull()`:只匹配 null
- `toBeUndefined()`:只匹配 undefined
- `toBeDefined()`:非 undefined
- `toBeTruthy()`:真值
- `toBeFalsy()`:假值
**数字匹配器:**
- `toBeGreaterThan(number)`:大于
- `toBeGreaterThanOrEqual(number)`:大于等于
- `toBeLessThan(number)`:小于
- `toBeLessThanOrEqual(number)`:小于等于
- `toBeCloseTo(number, precision)`:浮点数近似相等
**字符串匹配器:**
- `toMatch(regexp | string)`:匹配正则或字符串
- `toContain(item)`:包含元素或子字符串
**数组匹配器:**
- `toContain(item)`:包含元素
- `toContainEqual(item)`:包含相等元素
- `toHaveLength(number)`:数组长度
- `toBeArray()`:是数组
**对象匹配器:**
- `toHaveProperty(keyPath, value)`:有特定属性
- `toMatchObject(object)`:部分匹配对象
**函数匹配器:**
- `toHaveBeenCalled()`:被调用
- `toHaveBeenCalledWith(...args)`:用特定参数调用
- `toHaveBeenCalledTimes(n)`:调用次数
- `toHaveReturned()`:返回值
- `toHaveReturnedWith(value)`:返回特定值
**异常匹配器:**
- `toThrow(error?)`:抛出错误
- `toThrowErrorMatchingSnapshot()`:错误快照
**自定义匹配器:**
```javascript
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () => pass
? `expected ${received} not to be within range ${floor}-${ceiling}`
: `expected ${received} to be within range ${floor}-${ceiling}`
};
}
});
test('number within range', () => {
expect(100).toBeWithinRange(90, 110);
});
```
**否定匹配器:**
所有匹配器都可以使用 `.not` 进行否定:
```javascript
expect(value).not.toBe(42);
expect(array).not.toContain('item');
```
服务端 · 2月21日 15:57
如何在 Jest 中测试 React Hooks?如何使用 renderHook 和 act?Jest 提供了多种测试 React Hooks 的方法,主要使用 `@testing-library/react-hooks`:
**1. 测试 useState:**
```javascript
import { renderHook, act } from '@testing-library/react-hooks';
test('useState hook', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
```
**2. 测试 useEffect:**
```javascript
test('useEffect hook', () => {
const { result } = renderHook(() => useFetch('/api/data'));
expect(result.current.loading).toBe(true);
await act(async () => {
await waitFor(() => !result.current.loading);
});
expect(result.current.data).toBeDefined();
});
```
**3. 测试 useContext:**
```javascript
test('useContext hook', () => {
const wrapper = ({ children }) => (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current.theme).toBe('dark');
});
```
**4. 测试自定义 Hook:**
```javascript
function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);
const double = useMemo(() => value * 2, [value]);
return { value, setValue, double };
}
test('custom hook', () => {
const { result } = renderHook(() => useCustomHook(5));
expect(result.current.value).toBe(5);
expect(result.current.double).toBe(10);
act(() => {
result.current.setValue(10);
});
expect(result.current.double).toBe(20);
});
```
**5. 测试异步 Hook:**
```javascript
test('async hook', async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncData());
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toBeDefined();
});
```
**6. 测试错误处理:**
```javascript
test('hook error handling', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetch('/invalid'));
await waitForNextUpdate();
expect(result.current.error).toBeInstanceOf(Error);
});
```
**最佳实践:**
- 使用 `renderHook` 测试 Hook
- 使用 `act` 包装状态更新
- 测试 Hook 的初始状态和更新后的状态
- 测试异步 Hook 时使用 `waitFor` 或 `waitForNextUpdate`
- 测试错误边界情况
- 保持测试简单,专注于 Hook 的行为
服务端 · 2月21日 15:57
如何在 Jest 中测试定时器(setTimeout、setInterval)?如何使用假定时器?Jest 提供了多种处理定时器的方法,用于测试涉及 setTimeout、setInterval 等定时器的代码:
**1. 使用假定时器:**
```javascript
jest.useFakeTimers();
test('timer callback', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.runAllTimers();
expect(callback).toHaveBeenCalledTimes(1);
});
```
**2. 运行所有定时器:**
```javascript
jest.runAllTimers(); // 运行所有待处理的定时器
```
**3. 运行到特定时间:**
```javascript
jest.advanceTimersByTime(1000); // 前进 1000ms
jest.runOnlyPendingTimers(); // 只运行当前待处理的定时器
```
**4. 测试 setInterval:**
```javascript
test('interval timer', () => {
const callback = jest.fn();
setInterval(callback, 1000);
jest.advanceTimersByTime(3000);
expect(callback).toHaveBeenCalledTimes(3);
});
```
**5. 清理定时器:**
```javascript
afterEach(() => {
jest.useRealTimers(); // 恢复真实定时器
jest.clearAllTimers(); // 清除所有定时器
});
```
**6. 测试定时器清除:**
```javascript
test('clear timeout', () => {
const callback = jest.fn();
const timeoutId = setTimeout(callback, 1000);
clearTimeout(timeoutId);
jest.runAllTimers();
expect(callback).not.toHaveBeenCalled();
});
```
**最佳实践:**
- 在测试套件开始时使用 `jest.useFakeTimers()`
- 测试完成后恢复真实定时器
- 使用 `advanceTimersByTime` 测试时间依赖逻辑
- 确保清理所有定时器以避免内存泄漏
服务端 · 2月21日 15:57
Jest 中的生命周期钩子有哪些?beforeAll、afterAll、beforeEach 和 afterEach 如何使用?Jest 提供了多个生命周期钩子函数来管理测试的设置和清理:
**beforeAll:**
- 在当前测试套件的所有测试运行之前执行一次
- 适用于一次性设置,如数据库连接
- 示例:`beforeAll(() => { connectDatabase(); });`
**afterAll:**
- 在当前测试套件的所有测试运行之后执行一次
- 适用于清理资源,如关闭数据库连接
- 示例:`afterAll(() => { disconnectDatabase(); });`
**beforeEach:**
- 在当前测试套件的每个测试运行之前执行
- 适用于每个测试的独立设置,如重置状态
- 示例:`beforeEach(() => { resetState(); });`
**afterEach:**
- 在当前测试套件的每个测试运行之后执行
- 适用于每个测试的清理,如清除 Mock
- 示例:`afterEach(() => { jest.clearAllMocks(); });`
**作用域:**
- 钩子函数在定义的 `describe` 块内生效
- 嵌套的 `describe` 会继承父级钩子
- 可以在多个层级定义钩子
**示例:**
```javascript
describe('User API', () => {
beforeAll(() => setupServer());
afterAll(() => teardownServer());
beforeEach(() => {
jest.clearAllMocks();
});
test('should create user', () => { /* ... */ });
test('should get user', () => { /* ... */ });
});
```
**最佳实践:**
- 使用 `beforeEach` 和 `afterEach` 确保测试隔离
- 使用 `beforeAll` 和 `afterAll` 优化性能
- 在 `afterEach` 中清理 Mock 和定时器
- 保持钩子函数简单,避免复杂逻辑
服务端 · 2月21日 15:57
如何在 Jest 中进行参数化测试?如何使用 test.each 和 describe.each?Jest 提供了参数化测试的方法,可以使用 `test.each` 或 `describe.each` 来运行多组测试数据:
**1. 使用 test.each 进行参数化测试:**
```javascript
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('adds %i + %i = %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
```
**2. 使用对象数组:**
```javascript
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 1, expected: 3 },
])('$a + $b = $expected', ({ a, b, expected }) => {
expect(add(a, b)).toBe(expected);
});
```
**3. 使用 describe.each 进行分组测试:**
```javascript
describe.each([
['node', 'node'],
['jsdom', 'browser'],
])('test environment: %s', (env, type) => {
test(`runs in ${type} environment`, () => {
expect(process.env.NODE_ENV).toBeDefined();
});
});
```
**4. 测试表格数据:**
```javascript
test.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`('returns $expected when $a is added to $b', ({ a, b, expected }) => {
expect(add(a, b)).toBe(expected);
});
```
**5. 测试边界情况:**
```javascript
test.each([
[0, 0, 0],
[Number.MAX_SAFE_INTEGER, 1, Number.MAX_SAFE_INTEGER + 1],
[Number.MIN_SAFE_INTEGER, -1, Number.MIN_SAFE_INTEGER - 1],
])('handles edge cases: %i + %i = %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
```
**6. 测试错误情况:**
```javascript
test.each([
[undefined, 'input is required'],
[null, 'input is required'],
['', 'input cannot be empty'],
])('throws error for invalid input: %p', (input, expectedError) => {
expect(() => validate(input)).toThrow(expectedError);
});
```
**最佳实践:**
- 使用参数化测试减少重复代码
- 清晰描述测试数据和预期结果
- 测试正常情况和边界情况
- 使用表格格式提高可读性
- 保持测试数据简洁明了
服务端 · 2月21日 15:57
Jest 中常用的断言方法有哪些?如何使用 expect 和匹配器?Jest 提供了丰富的断言方法,主要通过 `expect()` 函数配合匹配器(matchers)使用:
**常用匹配器:**
**相等性匹配器:**
- `toBe(value)`:严格相等(使用 `===`)
- `toEqual(value)`:深度相等(递归比较对象和数组)
- `toMatchObject(object)`:部分匹配对象属性
- `toStrictEqual(value)`:严格深度相等(包括 undefined 属性)
**真值匹配器:**
- `toBeNull()`:只匹配 null
- `toBeUndefined()`:只匹配 undefined
- `toBeDefined()`:非 undefined
- `toBeTruthy()`:真值(非 false、0、""、null、undefined、NaN)
- `toBeFalsy()`:假值
**数字匹配器:**
- `toBeGreaterThan(number)`:大于
- `toBeGreaterThanOrEqual(number)`:大于等于
- `toBeLessThan(number)`:小于
- `toBeLessThanOrEqual(number)`:小于等于
- `toBeCloseTo(number, precision)`:浮点数近似相等
**字符串匹配器:**
- `toMatch(regexp | string)`:匹配正则或字符串
- `toContain(item)`:包含元素
**异步匹配器:**
- `resolves`:Promise 成功
- `rejects`:Promise 失败
**示例:**
```javascript
expect(2 + 2).toBe(4);
expect({ name: 'John' }).toEqual({ name: 'John' });
expect(null).toBeNull();
expect(10).toBeGreaterThan(5);
expect('hello').toMatch(/ell/);
```
服务端 · 2月21日 15:57
什么是 Jest 快照测试?如何使用快照测试来验证组件输出?Jest 的快照测试(Snapshot Testing)是一种强大的测试工具,用于确保 UI 组件或数据结构不会意外改变:
**快照测试原理:**
- 首次运行时,Jest 会捕获组件的渲染输出并保存为快照文件
- 后续运行时,将当前输出与保存的快照进行比较
- 如果输出发生变化,测试会失败并提示差异
**基本用法:**
```javascript
import renderer from 'react-test-renderer';
import Button from './Button';
test('Button snapshot', () => {
const tree = renderer
.create(<Button>Click me</Button>)
.toJSON();
expect(tree).toMatchSnapshot();
});
```
**内联快照:**
```javascript
test('inline snapshot', () => {
const data = { name: 'John', age: 30 };
expect(data).toMatchInlineSnapshot(`
Object {
"age": 30,
"name": "John",
}
`);
});
```
**快照更新:**
- 交互式更新:`jest -u` 或 `jest --updateSnapshot`
- CI/CD 中:使用 `--ci` 标志防止意外更新
**最佳实践:**
- 快照应该简洁且有意义
- 避免在快照中包含动态数据(如时间戳、ID)
- 定期审查和更新过时的快照
- 对于大型组件,考虑使用 `toMatchSnapshot({mode: 'deep'})`
- 使用自定义序列化器处理特殊对象
**适用场景:**
- React/Vue 组件渲染测试
- API 响应数据结构验证
- 配置文件结构检查
服务端 · 2月21日 15:54
Jest 中的 Mock 功能如何使用?如何创建 Mock 函数和模拟模块?Jest 的 Mock 功能是测试中隔离依赖和验证行为的重要工具:
**1. 创建 Mock 函数:**
```javascript
const mockFn = jest.fn();
mockFn('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledTimes(1);
```
**2. Mock 返回值:**
```javascript
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
mockFn.mockReturnValueOnce(10).mockReturnValueOnce(20);
// 或使用 mockResolvedValue 处理 Promise
mockFn.mockResolvedValue('data');
```
**3. Mock 实现:**
```javascript
const mockFn = jest.fn((a, b) => a + b);
// 或使用 mockImplementation
mockFn.mockImplementation((a, b) => a * b);
```
**4. Mock 模块:**
```javascript
jest.mock('./api', () => ({
fetchData: jest.fn(() => Promise.resolve('data'))
}));
import { fetchData } from './api';
```
**5. Spy 函数:**
```javascript
const spy = jest.spyOn(object, 'method');
spy.mockRestore(); // 恢复原函数
```
**常用断言:**
- `toHaveBeenCalled()`:被调用
- `toHaveBeenCalledWith(...args)`:用特定参数调用
- `toHaveBeenCalledTimes(n)`:调用次数
- `toHaveReturnedWith(value)`:返回特定值
- `toHaveLastReturnedWith(value)`:最后一次返回值
**最佳实践:**
- Mock 外部依赖,不 Mock 被测代码
- 测试完成后清理 Mock
- 使用 `jest.clearAllMocks()` 清除调用记录
- 使用 `jest.restoreAllMocks()` 恢复原始实现
服务端 · 2月21日 15:54
什么是 Jest 测试框架?它有哪些核心特性?Jest 是 Facebook 开发的 JavaScript 测试框架,它具有零配置、内置断言库、快照测试、模拟系统和覆盖率报告等特点。Jest 支持并行测试执行,可以显著提高测试速度。它还提供了强大的 CLI 工具和丰富的 API,使得编写测试变得简单直观。Jest 适用于测试 React 组件、Node.js 应用程序以及各种 JavaScript 项目。
**核心特性:**
- 零配置:开箱即用,无需复杂配置
- 内置断言:提供丰富的断言方法
- 快照测试:轻松捕获组件输出
- 模拟系统:强大的 mock 和 spy 功能
- 并行执行:自动并行运行测试
- 覆盖率报告:内置代码覆盖率统计
**使用场景:**
Jest 广泛应用于前端测试,特别是 React 项目的单元测试、集成测试和端到端测试。它也适用于 Node.js 后端服务的测试。
服务端 · 2月21日 15:54
如何在 Jest 中生成和配置代码覆盖率报告?覆盖率指标有哪些?Jest 提供了强大的代码覆盖率报告功能,帮助开发者了解测试覆盖情况:
**生成覆盖率报告:**
```bash
# 运行测试并生成覆盖率报告
jest --coverage
# 或在 package.json 中配置
{
"scripts": {
"test:coverage": "jest --coverage"
}
}
```
**覆盖率指标:**
- **Statements(语句覆盖率)**:被执行的代码语句百分比
- **Branches(分支覆盖率)**:被执行的条件分支百分比
- **Functions(函数覆盖率)**:被调用的函数百分比
- **Lines(行覆盖率)**:被执行的代码行百分比
**配置覆盖率:**
```javascript
// jest.config.js
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{js,jsx,ts,tsx}',
'!src/index.{js,jsx,ts,tsx}'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
coverageReporters: ['text', 'lcov', 'html']
};
```
**覆盖率报告格式:**
- `text`:命令行文本输出
- `lcov`:生成 lcov.info 文件,用于 CI/CD 工具
- `html`:生成 HTML 报告,可视化查看
- `json`:JSON 格式报告
**最佳实践:**
- 设置合理的覆盖率阈值(通常 80%)
- 排除不需要测试的文件(配置文件、类型定义等)
- 在 CI/CD 中集成覆盖率检查
- 定期审查未覆盖的代码
- 关注核心业务逻辑的覆盖率
服务端 · 2月21日 15:54