5月27日 19:50
Jest 生命周期钩子有哪些?beforeAll、afterAll、beforeEach 和 afterEach 怎么用?
Jest 提供了四个生命周期钩子函数,用于在测试的不同阶段执行设置和清理操作。理解它们的执行时机和使用场景,是编写可靠测试的基础。
四个钩子函数概览
| 钩子 | 执行时机 | 典型用途 |
|---|---|---|
beforeAll | 当前 describe 块所有测试运行前,仅执行一次 | 建立数据库连接、启动服务器 |
afterAll | 当前 describe 块所有测试运行后,仅执行一次 | 关闭数据库连接、停止服务器 |
beforeEach | 当前 describe 块每个测试运行前,每次都执行 | 重置状态、初始化数据 |
afterEach | 当前 describe 块每个测试运行后,每次都执行 | 清除 Mock、还原定时器 |
beforeAll 与 afterAll:一次性设置与清理
beforeAll 适合需要一次投入成本的场景,避免在每个测试前重复执行:
javascriptlet db; beforeAll(async () => { db = await connectDatabase('test_db'); await db.createTables(); }); afterAll(async () => { await db.dropTables(); await db.close(); }); test('should insert user', async () => { await db.insert('users', { name: 'Alice' }); const users = await db.query('SELECT * FROM users'); expect(users).toHaveLength(1); });
注意: beforeAll 中如果发生错误,该 describe 块内的所有测试都会失败。
beforeEach 与 afterEach:逐测试隔离
beforeEach 和 afterEach 保证每个测试在独立环境中运行,是测试隔离的核心手段:
javascriptlet users; beforeEach(() => { users = [{ id: 1, name: 'Alice' }]; }); afterEach(() => { jest.clearAllMocks(); jest.useRealTimers(); }); test('should add user', () => { users.push({ id: 2, name: 'Bob' }); expect(users).toHaveLength(2); }); test('should not be affected by previous test', () => { // beforeEach 重置了 users,这里仍然是初始状态 expect(users).toHaveLength(1); });
钩子的执行顺序
当存在嵌套 describe 时,钩子按照从外到内的顺序执行 setup,从内到外的顺序执行 teardown:
javascriptdescribe('Outer', () => { beforeAll(() => console.log('Outer beforeAll')); beforeEach(() => console.log('Outer beforeEach')); afterEach(() => console.log('Outer afterEach')); afterAll(() => console.log('Outer afterAll')); describe('Inner', () => { beforeAll(() => console.log('Inner beforeAll')); beforeEach(() => console.log('Inner beforeEach')); afterEach(() => console.log('Inner afterEach')); afterAll(() => console.log('Inner afterAll')); test('example', () => { console.log('--- test runs ---'); }); }); });
执行顺序输出:
shellOuter beforeAll Inner beforeAll Outer beforeEach Inner beforeEach --- test runs --- Inner afterEach Outer afterEach Inner afterAll Outer afterAll
关键规则: 外层 beforeEach 先于内层执行,外层 afterEach 后于内层执行——这保证了内层可以依赖外层的设置,同时内层的清理不会影响外层。
异步钩子
钩子函数支持异步操作,三种写法均可:
javascript// 方式一:async/await(推荐) beforeAll(async () => { await initializeService(); }); // 方式二:返回 Promise beforeAll(() => { return fetch('/api/setup').then(res => res.json()); }); // 方式三:单个参数 done 回调 beforeAll((done) => { startServer(done); });
如果异步钩子超时,可以设置自定义超时时间:
javascriptbeforeAll(async () => { await heavySetup(); }, 30000); // 30 秒超时
常见陷阱
1. 在 beforeAll 中修改共享状态,在 afterEach 中忘记清理
javascript// 错误:beforeAll 修改了全局状态,但 afterAll 没有还原 beforeAll(() => { process.env.NODE_ENV = 'test'; }); // 其他测试文件可能受到影响 // 正确:配对使用 afterAll 还原 beforeAll(() => { originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'test'; }); afterAll(() => { process.env.NODE_ENV = originalEnv; });
2. 混淆 beforeAll 和 beforeEach 的使用场景
beforeAll:设置成本高、测试之间可共享(数据库连接、服务器启动)beforeEach:每个测试需要独立副本(状态重置、数据初始化)
如果测试之间有依赖或顺序敏感,优先使用 beforeEach 保证隔离。
3. 钩子中的错误导致测试全部跳过
beforeAll 抛出错误时,该 describe 块内所有测试直接失败。如果部分初始化失败不应阻断所有测试,考虑将初始化移入 beforeEach 并做容错处理。
最佳实践
- 优先使用 beforeEach/afterEach 保证隔离,仅在设置成本确实很高时才用 beforeAll
- afterEach 中务必清理 Mock 和定时器:
jest.clearAllMocks()+jest.useRealTimers() - 保持钩子函数简洁,复杂逻辑拆分为辅助函数
- 配对使用:有 before 就有对应的 after,确保资源不泄漏
- 避免在钩子间传递状态,每个测试应能独立运行