Jest 教程
1. 核心知识点讲解
1.1 Jest 简介
Jest 是 Facebook 开发的一个功能丰富的 JavaScript 测试框架,专注于简洁性和易用性。它提供了完整的测试解决方案,包括单元测试、集成测试和快照测试,并且内置了 Mock 功能和测试覆盖率报告。
1.2 安装和配置
安装 Jest:
npm install --save-dev jest配置 package.json:
{
"scripts": {
"test": "jest"
}
}创建 jest.config.js 配置文件:
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)']
};1.3 基本测试结构
测试文件命名约定:
__tests__目录下的文件- 以
.test.js或.spec.js结尾的文件
基本测试结构:
test('测试描述', () => {
// 测试代码
expect(1 + 1).toBe(2);
});
// 或使用 it 别名
it('测试描述', () => {
expect(1 + 1).toBe(2);
});1.4 断言方法
Jest 提供了丰富的断言方法:
expect(value).toBe(expected)- 严格相等expect(value).toEqual(expected)- 深度相等expect(value).toBeTruthy()- 真值expect(value).toBeFalsy()- 假值expect(value).toBeNull()- 为 nullexpect(value).toBeUndefined()- 为 undefinedexpect(value).toBeDefined()- 已定义expect(value).toContain(item)- 包含指定项expect(value).toMatch(regexp)- 匹配正则表达式expect(value).toThrow(error)- 抛出错误
1.5 测试生命周期
测试钩子:
describe('测试套件', () => {
beforeAll(() => {
// 在所有测试开始前运行
});
afterAll(() => {
// 在所有测试结束后运行
});
beforeEach(() => {
// 在每个测试开始前运行
});
afterEach(() => {
// 在每个测试结束后运行
});
test('测试用例 1', () => {
// 测试代码
});
test('测试用例 2', () => {
// 测试代码
});
});1.6 Mock 功能
Mock 函数:
const mockFn = jest.fn();
mockFn.mockReturnValue('默认返回值');
mockFn.mockResolvedValue('异步返回值');
mockFn.mockRejectedValue(new Error('错误信息'));Mock 模块:
// __mocks__/fs.js
module.exports = {
readFileSync: jest.fn(() => '模拟文件内容')
};
// 在测试文件中
jest.mock('fs');
const fs = require('fs');
test('测试读取文件', () => {
expect(fs.readFileSync('test.txt')).toBe('模拟文件内容');
});1.7 快照测试
快照测试:
test('快照测试', () => {
const component = {
name: '测试组件',
props: { value: 123 }
};
expect(component).toMatchSnapshot();
});更新快照:
npm test -- -u1.8 测试覆盖率
运行测试并生成覆盖率报告:
npm test -- --coverage覆盖率报告指标:
- 语句覆盖率 (Statement Coverage)
- 分支覆盖率 (Branch Coverage)
- 函数覆盖率 (Function Coverage)
- 行覆盖率 (Line Coverage)
2. 实用案例分析
2.1 基本测试案例
场景: 测试一个简单的计算器函数
实现:
// src/calculator.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
module.exports = {
add,
subtract,
multiply,
divide
};
// src/__tests__/calculator.test.js
const { add, subtract, multiply, divide } = require('../calculator');
describe('计算器测试', () => {
test('加法测试', () => {
expect(add(1, 2)).toBe(3);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('减法测试', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(1, 5)).toBe(-4);
expect(subtract(0, 0)).toBe(0);
});
test('乘法测试', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(-1, 5)).toBe(-5);
expect(multiply(0, 100)).toBe(0);
});
test('除法测试', () => {
expect(divide(6, 2)).toBe(3);
expect(divide(5, 2)).toBe(2.5);
expect(divide(-10, 2)).toBe(-5);
});
test('除法测试 - 除数为零', () => {
expect(() => divide(1, 0)).toThrow('除数不能为零');
});
});2.2 Mock 测试案例
场景: 测试一个依赖外部 API 的函数
实现:
// src/api.js
const axios = require('axios');
async function fetchUser(userId) {
const response = await axios.get(`https://api.example.com/users/${userId}`);
return response.data;
}
module.exports = { fetchUser };
// src/__tests__/api.test.js
const axios = require('axios');
const { fetchUser } = require('../api');
// Mock axios
jest.mock('axios');
describe('API 测试', () => {
test('获取用户信息成功', async () => {
// 模拟响应数据
const mockUser = { id: 1, name: '张三' };
axios.get.mockResolvedValue({ data: mockUser });
// 调用被测试函数
const user = await fetchUser(1);
// 验证结果
expect(user).toEqual(mockUser);
expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
expect(axios.get).toHaveBeenCalledTimes(1);
});
test('获取用户信息失败', async () => {
// 模拟错误响应
const errorMessage = '网络错误';
axios.get.mockRejectedValue(new Error(errorMessage));
// 验证错误
await expect(fetchUser(1)).rejects.toThrow(errorMessage);
expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
});
});2.3 快照测试案例
场景: 测试一个生成 HTML 的函数
实现:
// src/htmlGenerator.js
function generateUserCard(user) {
return `
<div class="user-card">
<h2>${user.name}</h2>
<p>邮箱: ${user.email}</p>
<p>年龄: ${user.age}</p>
</div>
`;
}
module.exports = { generateUserCard };
// src/__tests__/htmlGenerator.test.js
const { generateUserCard } = require('../htmlGenerator');
describe('HTML 生成器测试', () => {
test('生成用户卡片 HTML', () => {
const user = {
name: '张三',
email: 'zhangsan@example.com',
age: 30
};
// 快照测试
expect(generateUserCard(user)).toMatchSnapshot();
});
});3. 代码示例
3.1 完整的测试项目结构
项目结构:
src/
├── calculator.js
├── api.js
├── htmlGenerator.js
└── __tests__/
├── calculator.test.js
├── api.test.js
└── htmlGenerator.test.js3.2 运行测试
运行所有测试:
npm test运行特定测试文件:
npm test src/__tests__/calculator.test.js运行测试并生成覆盖率报告:
npm test -- --coverage更新快照:
npm test -- -u3.3 测试配置示例
jest.config.js:
module.exports = {
// 测试环境
testEnvironment: 'node',
// 测试文件匹配模式
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)'
],
// 测试文件路径忽略模式
testPathIgnorePatterns: [
'/node_modules/',
'/coverage/'
],
// 模块名称映射
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
// 覆盖率报告配置
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js'
],
// 覆盖率阈值
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
// 测试超时时间
testTimeout: 5000,
// 显示测试结果的详细程度
verbose: true
};3.4 高级测试技巧
测试异步代码:
// 方法 1: 使用 async/await
test('异步测试 - async/await', async () => {
const promise = Promise.resolve('成功');
expect(await promise).toBe('成功');
});
// 方法 2: 使用 .resolves/.rejects
test('异步测试 - .resolves', () => {
const promise = Promise.resolve('成功');
expect(promise).resolves.toBe('成功');
});
test('异步测试 - .rejects', () => {
const promise = Promise.reject(new Error('失败'));
expect(promise).rejects.toThrow('失败');
});
// 方法 3: 使用回调
test('异步测试 - 回调', (done) => {
setTimeout(() => {
expect(1 + 1).toBe(2);
done();
}, 100);
});测试定时器:
test('定时器测试', () => {
// 模拟定时器
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
// 快进定时器
jest.advanceTimersByTime(1000);
// 验证回调被调用
expect(callback).toHaveBeenCalledTimes(1);
// 恢复真实定时器
jest.useRealTimers();
});测试 DOM 操作:
// src/domUtils.js
function createElement(tagName, text) {
const element = document.createElement(tagName);
element.textContent = text;
return element;
}
function appendElement(parent, child) {
parent.appendChild(child);
}
module.exports = { createElement, appendElement };
// src/__tests__/domUtils.test.js
const { createElement, appendElement } = require('../domUtils');
describe('DOM 工具测试', () => {
test('创建元素', () => {
// 设置 DOM 环境
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
const element = createElement('div', '测试文本');
expect(element.tagName).toBe('DIV');
expect(element.textContent).toBe('测试文本');
});
test('追加元素', () => {
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
const parent = document.createElement('div');
const child = document.createElement('span');
child.textContent = '子元素';
appendElement(parent, child);
expect(parent.children.length).toBe(1);
expect(parent.firstChild).toBe(child);
});
});4. 总结
Jest 是一个功能强大的 JavaScript 测试框架,提供了完整的测试解决方案,包括:
- 简洁易用的测试语法
- 内置的 Mock 功能
- 快照测试
- 测试覆盖率报告
- 异步测试支持
- 定时器模拟
通过学习本教程,你可以开始在项目中使用 Jest 编写测试,提高代码质量和可靠性。良好的测试实践可以帮助你:
- 及早发现并修复 bug
- 提高代码的可维护性
- 促进团队协作
- 减少重构风险
随着实践的深入,你会发现测试不仅是保证代码质量的重要手段,也是提高开发效率的有效工具。
5. 进一步学习资源
希望本教程对你学习 Jest 有所帮助!