Jest 测试生命周期 #
生命周期钩子概述 #
Jest 提供了四个生命周期钩子函数,用于在测试前后执行设置和清理操作:
text
┌─────────────────────────────────────────────────────────────┐
│ 测试执行顺序 │
├─────────────────────────────────────────────────────────────┤
│ beforeAll ──────────────────────────────────────────────▶ │
│ │
│ beforeEach ──▶ test ──▶ afterEach │
│ beforeEach ──▶ test ──▶ afterEach │
│ beforeEach ──▶ test ──▶ afterEach │
│ │
│ afterAll ──────────────────────────────────────────────▶ │
└─────────────────────────────────────────────────────────────┘
四个钩子函数 #
beforeAll #
在所有测试之前执行一次:
javascript
let database;
beforeAll(() => {
// 建立数据库连接
database = connectDatabase();
console.log('数据库连接已建立');
});
test('test 1', () => {
expect(database).toBeDefined();
});
test('test 2', () => {
expect(database.isConnected()).toBe(true);
});
afterAll #
在所有测试之后执行一次:
javascript
let server;
beforeAll(async () => {
server = await startServer();
});
afterAll(async () => {
await server.close();
console.log('服务器已关闭');
});
test('server is running', () => {
expect(server.listening).toBe(true);
});
beforeEach #
在每个测试之前执行:
javascript
let counter;
beforeEach(() => {
counter = 0;
console.log('counter 已重置');
});
test('increment', () => {
counter++;
expect(counter).toBe(1);
});
test('decrement', () => {
counter--;
expect(counter).toBe(-1);
});
// 每个测试开始时 counter 都是 0
afterEach #
在每个测试之后执行:
javascript
let users = [];
afterEach(() => {
users = [];
console.log('用户列表已清空');
});
test('add user', () => {
users.push({ name: 'John' });
expect(users).toHaveLength(1);
});
test('users list is empty', () => {
// afterEach 在上一个测试后执行,所以 users 是空的
expect(users).toHaveLength(0);
});
完整示例 #
javascript
describe('Database', () => {
let connection;
// 所有测试之前执行一次
beforeAll(async () => {
connection = await connectDatabase();
console.log('1. beforeAll - 数据库连接');
});
// 所有测试之后执行一次
afterAll(async () => {
await connection.close();
console.log('6. afterAll - 关闭连接');
});
// 每个测试之前执行
beforeEach(async () => {
await connection.clear();
console.log('2/4. beforeEach - 清空数据');
});
// 每个测试之后执行
afterEach(() => {
console.log('3/5. afterEach - 清理完成');
});
test('insert user', async () => {
console.log('3. test - 插入用户');
await connection.insert({ name: 'John' });
const users = await connection.findAll();
expect(users).toHaveLength(1);
});
test('find users', async () => {
console.log('5. test - 查找用户');
const users = await connection.findAll();
expect(users).toHaveLength(0);
});
});
// 执行顺序:
// 1. beforeAll
// 2. beforeEach
// 3. test (insert user)
// 4. afterEach
// 5. beforeEach
// 6. test (find users)
// 7. afterEach
// 8. 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
钩子执行顺序 #
text
┌─────────────────────────────────────────────────────────────┐
│ 执行顺序(由外到内) │
├─────────────────────────────────────────────────────────────┤
│ 1. outer beforeAll │
│ 2. outer beforeEach │
│ 3. inner beforeAll │
│ 4. outer beforeEach │
│ 5. inner beforeEach │
│ 6. [TEST] │
│ 7. inner afterEach │
│ 8. outer afterEach │
│ 9. inner afterAll │
│ 10. outer afterAll │
└─────────────────────────────────────────────────────────────┘
异步钩子 #
Promise #
javascript
beforeAll(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log('异步初始化完成');
resolve();
}, 1000);
});
});
async/await #
javascript
let db;
beforeAll(async () => {
db = await connectDatabase();
await db.migrate();
});
afterAll(async () => {
await db.close();
});
test('database is connected', () => {
expect(db.isConnected()).toBe(true);
});
回调函数 #
javascript
beforeAll(done => {
setTimeout(() => {
console.log('初始化完成');
done();
}, 1000);
});
实际应用场景 #
数据库测试 #
javascript
describe('User Model', () => {
let db;
let User;
beforeAll(async () => {
// 连接测试数据库
db = await mongoose.connect('mongodb://localhost:27017/test');
User = mongoose.model('User', userSchema);
});
afterAll(async () => {
// 关闭连接
await mongoose.connection.close();
});
beforeEach(async () => {
// 清空数据
await User.deleteMany({});
});
test('should create user', async () => {
const user = await User.create({ name: 'John' });
expect(user.name).toBe('John');
});
test('should find user', async () => {
await User.create({ name: 'John' });
const user = await User.findOne({ name: 'John' });
expect(user).toBeDefined();
});
});
API 测试 #
javascript
describe('API Endpoints', () => {
let server;
let request;
beforeAll(async () => {
server = await createServer();
request = supertest(server);
});
afterAll(async () => {
await server.close();
});
describe('GET /api/users', () => {
test('should return users', async () => {
const response = await request.get('/api/users');
expect(response.status).toBe(200);
});
});
describe('POST /api/users', () => {
test('should create user', async () => {
const response = await request
.post('/api/users')
.send({ name: 'John' });
expect(response.status).toBe(201);
});
});
});
组件测试 #
javascript
describe('Button Component', () => {
let container;
beforeEach(() => {
// 创建容器
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
// 清理容器
document.body.removeChild(container);
container = null;
});
test('renders button', () => {
render(<Button>Click me</Button>, container);
expect(container.querySelector('button')).toBeDefined();
});
});
定时器测试 #
javascript
describe('Timer', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('calls callback after delay', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
});
清理操作 #
清理 Mock #
javascript
afterEach(() => {
// 清理所有 Mock
jest.clearAllMocks();
});
afterAll(() => {
// 恢复所有 Mock
jest.restoreAllMocks();
});
清理模块缓存 #
javascript
beforeEach(() => {
// 重置模块注册表
jest.resetModules();
});
清理 DOM #
javascript
afterEach(() => {
// 清理渲染的组件
cleanup();
});
条件执行 #
动态跳过钩子 #
javascript
let db;
beforeAll(async () => {
try {
db = await connectDatabase();
} catch (error) {
// 如果连接失败,跳过所有测试
console.error('Database connection failed');
}
});
test('database operations', () => {
if (!db) {
test.skip('Database not available');
return;
}
// 测试代码
});
环境判断 #
javascript
const isCI = process.env.CI === 'true';
beforeAll(async () => {
if (isCI) {
// CI 环境的特殊设置
await setupCI();
} else {
// 本地开发环境设置
await setupLocal();
}
});
钩子函数对比 #
| 钩子 | 执行时机 | 执行次数 | 用途 |
|---|---|---|---|
| beforeAll | 所有测试前 | 1次 | 建立连接、启动服务 |
| afterAll | 所有测试后 | 1次 | 关闭连接、停止服务 |
| beforeEach | 每个测试前 | N次 | 重置状态、初始化数据 |
| afterEach | 每个测试后 | N次 | 清理数据、恢复状态 |
最佳实践 #
1. 避免共享状态 #
javascript
// ❌ 不好的做法
describe('bad example', () => {
let counter = 0;
test('increment', () => {
counter++;
expect(counter).toBe(1);
});
test('double', () => {
// 依赖上一个测试的状态
counter *= 2;
expect(counter).toBe(2); // 可能失败
});
});
// ✅ 好的做法
describe('good example', () => {
let counter;
beforeEach(() => {
counter = 0;
});
test('increment', () => {
counter++;
expect(counter).toBe(1);
});
test('double', () => {
counter = 1;
counter *= 2;
expect(counter).toBe(2);
});
});
2. 合理使用钩子 #
javascript
// ❌ 不好的做法 - 过度使用 beforeEach
describe('overusing beforeEach', () => {
let a, b, c, d;
beforeEach(() => {
a = 1;
b = 2;
c = 3;
d = 4;
});
test('only uses a', () => {
expect(a).toBe(1);
});
});
// ✅ 好的做法 - 只在测试中初始化需要的数据
describe('better approach', () => {
test('uses a', () => {
const a = 1;
expect(a).toBe(1);
});
});
3. 清理资源 #
javascript
describe('resource management', () => {
let resources = [];
beforeEach(() => {
resources = [];
});
afterEach(() => {
// 确保清理所有资源
resources.forEach(resource => {
resource.close();
});
resources = [];
});
test('creates resource', () => {
const resource = createResource();
resources.push(resource);
expect(resource.isOpen()).toBe(true);
});
});
4. 错误处理 #
javascript
describe('error handling', () => {
let db;
beforeAll(async () => {
try {
db = await connectDatabase();
} catch (error) {
console.error('Failed to connect:', error);
throw error; // 让测试失败
}
});
afterAll(async () => {
if (db) {
try {
await db.close();
} catch (error) {
console.error('Failed to close:', error);
}
}
});
});
常见问题 #
钩子中的错误 #
javascript
describe('error in hook', () => {
beforeAll(() => {
throw new Error('Setup failed');
});
test('this test will not run', () => {
expect(true).toBe(true);
});
});
钩子顺序问题 #
javascript
describe('hook order', () => {
let value = 0;
beforeAll(() => {
value = 1;
});
beforeEach(() => {
value++;
});
test('first test', () => {
console.log(value); // 2
});
test('second test', () => {
console.log(value); // 3
});
});
异步清理 #
javascript
describe('async cleanup', () => {
let server;
beforeAll(async () => {
server = await startServer();
});
afterAll(async () => {
// 确保异步清理完成
await new Promise(resolve => {
server.close(resolve);
});
});
});
下一步 #
现在你已经掌握了 Jest 测试生命周期,接下来学习 异步测试 处理异步代码的测试!
最后更新:2026-03-28