Jest 测试生命周期 #
生命周期概述 #
Jest 提供了四个生命周期钩子,用于在测试的不同阶段执行代码:
text
┌─────────────────────────────────────────────────────────────┐
│ 测试执行顺序 │
├─────────────────────────────────────────────────────────────┤
│ 1. beforeAll - 所有测试之前执行一次 │
│ 2. beforeEach - 每个测试之前执行 │
│ 3. test - 执行测试 │
│ 4. afterEach - 每个测试之后执行 │
│ 5. afterAll - 所有测试之后执行一次 │
└─────────────────────────────────────────────────────────────┘
基本钩子 #
beforeAll 和 afterAll #
在整个测试文件的开头和结尾各执行一次:
javascript
describe('Database Tests', () => {
let connection;
beforeAll(() => {
// 建立数据库连接
connection = connectToDatabase();
console.log('数据库连接已建立');
});
afterAll(() => {
// 关闭数据库连接
connection.close();
console.log('数据库连接已关闭');
});
test('test 1', () => {
expect(connection).toBeDefined();
});
test('test 2', () => {
expect(connection.isConnected()).toBe(true);
});
});
// 输出:
// 数据库连接已建立
// test 1 执行
// test 2 执行
// 数据库连接已关闭
beforeEach 和 afterEach #
在每个测试前后各执行一次:
javascript
describe('User Tests', () => {
let user;
beforeEach(() => {
// 每个测试前创建新用户
user = new User('John');
console.log('用户已创建');
});
afterEach(() => {
// 每个测试后清理用户
user = null;
console.log('用户已清理');
});
test('should have name', () => {
expect(user.name).toBe('John');
});
test('should update name', () => {
user.setName('Jane');
expect(user.name).toBe('Jane');
});
});
// 输出:
// 用户已创建
// should have name 执行
// 用户已清理
// 用户已创建
// should update name 执行
// 用户已清理
完整执行顺序 #
基本示例 #
javascript
describe('Lifecycle Demo', () => {
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('test 1', () => console.log('1 - test 1'));
test('test 2', () => console.log('1 - test 2'));
});
// 输出:
// 1 - beforeAll
// 1 - beforeEach
// 1 - test 1
// 1 - afterEach
// 1 - beforeEach
// 1 - test 2
// 1 - afterEach
// 1 - afterAll
嵌套 describe #
javascript
describe('Outer', () => {
beforeAll(() => console.log('Outer - beforeAll'));
afterAll(() => console.log('Outer - afterAll'));
beforeEach(() => console.log('Outer - beforeEach'));
afterEach(() => console.log('Outer - afterEach'));
test('outer test', () => console.log('Outer - test'));
describe('Inner', () => {
beforeAll(() => console.log('Inner - beforeAll'));
afterAll(() => console.log('Inner - afterAll'));
beforeEach(() => console.log('Inner - beforeEach'));
afterEach(() => console.log('Inner - afterEach'));
test('inner test', () => console.log('Inner - test'));
});
});
// 输出:
// Outer - beforeAll
// Outer - beforeEach
// Outer - test
// Outer - afterEach
// Inner - beforeAll
// Outer - beforeEach
// Inner - beforeEach
// Inner - test
// Inner - afterEach
// Outer - afterEach
// Inner - afterAll
// Outer - afterAll
异步钩子 #
回调方式 #
javascript
describe('Async Callback', () => {
let data;
beforeAll((done) => {
setTimeout(() => {
data = 'loaded';
done();
}, 100);
});
test('data should be loaded', () => {
expect(data).toBe('loaded');
});
});
Promise 方式 #
javascript
describe('Async Promise', () => {
let connection;
beforeAll(() => {
return connectDatabase().then(conn => {
connection = conn;
});
});
afterAll(() => {
return connection.close();
});
test('connection should be established', () => {
expect(connection).toBeDefined();
});
});
async/await 方式 #
javascript
describe('Async Await', () => {
let db;
beforeAll(async () => {
db = await connectDatabase();
await db.seedTestData();
});
afterAll(async () => {
await db.clear();
await db.close();
});
beforeEach(async () => {
await db.reset();
});
test('should find user', async () => {
const user = await db.findUser(1);
expect(user).toBeDefined();
});
});
实际应用场景 #
数据库测试 #
javascript
describe('Database Operations', () => {
let db;
beforeAll(async () => {
db = await connectDatabase();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
await db.seedTestData();
});
describe('User operations', () => {
test('should create user', async () => {
const user = await db.createUser({ name: 'John' });
expect(user.id).toBeDefined();
});
test('should find user', async () => {
const user = await db.findUser(1);
expect(user.name).toBe('Test User');
});
});
});
API 测试 #
javascript
describe('API Tests', () => {
let server;
beforeAll(async () => {
server = await startServer();
});
afterAll(async () => {
await server.close();
});
beforeEach(() => {
jest.clearAllMocks();
});
test('GET /users', async () => {
const response = await fetch('/api/users');
expect(response.status).toBe(200);
});
});
组件测试 #
javascript
describe('Component Tests', () => {
let wrapper;
afterEach(() => {
if (wrapper) {
wrapper.unmount();
wrapper = null;
}
});
test('renders correctly', () => {
wrapper = mount(<Button />);
expect(wrapper.exists()).toBe(true);
});
test('handles click', () => {
const onClick = jest.fn();
wrapper = mount(<Button onClick={onClick} />);
wrapper.simulate('click');
expect(onClick).toHaveBeenCalled();
});
});
Mock 清理 #
javascript
describe('Mock Cleanup', () => {
let mockFetch;
beforeEach(() => {
mockFetch = jest.spyOn(global, 'fetch');
});
afterEach(() => {
mockFetch.mockRestore();
});
test('fetches data', async () => {
mockFetch.mockResolvedValue({ data: 'test' });
const result = await fetchData();
expect(result).toEqual({ data: 'test' });
});
});
作用域规则 #
describe 块作用域 #
javascript
describe('Scope Demo', () => {
let outerVar = 'outer';
beforeEach(() => {
outerVar = 'outer';
});
test('outer test', () => {
expect(outerVar).toBe('outer');
});
describe('Inner', () => {
let innerVar = 'inner';
beforeEach(() => {
innerVar = 'inner';
});
test('inner test', () => {
expect(outerVar).toBe('outer');
expect(innerVar).toBe('inner');
});
});
});
变量共享 #
javascript
describe('Shared Variables', () => {
let sharedData;
beforeAll(() => {
sharedData = { users: [] };
});
beforeEach(() => {
sharedData.users.push({ id: 1 });
});
afterEach(() => {
sharedData.users = [];
});
test('test 1', () => {
expect(sharedData.users).toHaveLength(1);
});
test('test 2', () => {
expect(sharedData.users).toHaveLength(1);
});
});
条件执行 #
跳过钩子 #
javascript
describe('Skip Hooks', () => {
beforeAll(() => {
console.log('This will not run');
});
test.skip('skipped test', () => {
// 这个测试被跳过,beforeAll 不会执行
});
});
条件钩子 #
javascript
describe('Conditional Hooks', () => {
const shouldRunSlowTests = process.env.RUN_SLOW_TESTS === 'true';
beforeAll(() => {
if (!shouldRunSlowTests) {
return;
}
// 耗时的设置
});
(shouldRunSlowTests ? describe : describe.skip)('Slow Tests', () => {
test('slow test', () => {
// 测试代码
});
});
});
错误处理 #
钩子中的错误 #
javascript
describe('Error Handling', () => {
beforeAll(() => {
throw new Error('Setup failed');
});
test('this test will not run', () => {
// 如果 beforeAll 抛出错误,测试不会运行
});
});
错误恢复 #
javascript
describe('Error Recovery', () => {
let connection;
beforeAll(async () => {
try {
connection = await connectDatabase();
} catch (error) {
console.error('Connection failed:', error);
connection = null;
}
});
test('handles connection failure', () => {
if (connection === null) {
expect(true).toBe(true); // 跳过测试
} else {
expect(connection).toBeDefined();
}
});
});
超时设置 #
设置超时时间 #
javascript
describe('Timeout Settings', () => {
beforeAll(async () => {
await slowSetup();
}, 10000); // 10 秒超时
test('slow test', async () => {
const result = await slowOperation();
expect(result).toBeDefined();
}, 5000); // 5 秒超时
});
全局超时 #
javascript
// jest.config.js
module.exports = {
testTimeout: 10000, // 全局默认超时 10 秒
};
最佳实践 #
1. 保持钩子简洁 #
javascript
// ✅ 好的做法
beforeEach(() => {
user = new User('John');
});
// ❌ 不好的做法
beforeEach(() => {
user = new User('John');
user.setEmail('john@example.com');
user.setAge(30);
user.setAddress('123 Main St');
// 太多设置
});
2. 清理资源 #
javascript
describe('Resource Cleanup', () => {
let server;
let db;
beforeAll(async () => {
server = await startServer();
db = await connectDatabase();
});
afterAll(async () => {
await server.close();
await db.close();
});
});
3. 使用工厂函数 #
javascript
describe('Factory Functions', () => {
let user;
const createUser = (overrides = {}) => {
return new User({
name: 'John',
email: 'john@example.com',
...overrides,
});
};
beforeEach(() => {
user = createUser();
});
test('default user', () => {
expect(user.name).toBe('John');
});
test('custom user', () => {
const customUser = createUser({ name: 'Jane' });
expect(customUser.name).toBe('Jane');
});
});
4. 避免共享状态 #
javascript
// ❌ 不好的做法 - 共享状态
describe('Bad', () => {
let counter = 0;
test('increment', () => {
counter++;
expect(counter).toBe(1);
});
test('check value', () => {
// counter 可能是 1,取决于测试执行顺序
expect(counter).toBe(1);
});
});
// ✅ 好的做法 - 隔离状态
describe('Good', () => {
let counter;
beforeEach(() => {
counter = 0;
});
test('increment', () => {
counter++;
expect(counter).toBe(1);
});
test('check value', () => {
expect(counter).toBe(0);
});
});
调试技巧 #
日志输出 #
javascript
describe('Debug Logging', () => {
beforeEach(() => {
console.log('Test starting at:', new Date().toISOString());
});
afterEach(() => {
console.log('Test finished at:', new Date().toISOString());
});
test('example', () => {
console.log('Inside test');
expect(true).toBe(true);
});
});
追踪执行顺序 #
javascript
describe('Execution Order', () => {
const log = [];
beforeAll(() => log.push('beforeAll'));
afterAll(() => {
log.push('afterAll');
console.log('Execution order:', log);
});
beforeEach(() => log.push('beforeEach'));
afterEach(() => log.push('afterEach'));
test('test 1', () => log.push('test 1'));
test('test 2', () => log.push('test 2'));
});
常见问题 #
钩子执行多次 #
javascript
// 问题:beforeAll 在每个 describe 中都执行
describe('Outer', () => {
beforeAll(() => console.log('Outer beforeAll'));
describe('Inner 1', () => {
test('test', () => {});
});
describe('Inner 2', () => {
test('test', () => {});
});
});
// 解决方案:将共享的设置放在最外层
异步钩子未等待 #
javascript
// 问题:异步操作未完成
beforeAll(() => {
fetchData(); // 没有 return 或 await
});
// 解决方案
beforeAll(async () => {
await fetchData();
});
下一步 #
现在你已经掌握了 Jest 测试生命周期,接下来学习 异步测试 学习如何测试异步代码!
最后更新:2026-03-28