5月27日 19:50

Jest 生命周期钩子有哪些?beforeAll、afterAll、beforeEach 和 afterEach 怎么用?

Jest 提供了四个生命周期钩子函数,用于在测试的不同阶段执行设置和清理操作。理解它们的执行时机和使用场景,是编写可靠测试的基础。

四个钩子函数概览

钩子执行时机典型用途
beforeAll当前 describe 块所有测试运行前,仅执行一次建立数据库连接、启动服务器
afterAll当前 describe 块所有测试运行后,仅执行一次关闭数据库连接、停止服务器
beforeEach当前 describe 块每个测试运行前,每次都执行重置状态、初始化数据
afterEach当前 describe 块每个测试运行后,每次都执行清除 Mock、还原定时器

beforeAll 与 afterAll:一次性设置与清理

beforeAll 适合需要一次投入成本的场景,避免在每个测试前重复执行:

javascript
let 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:逐测试隔离

beforeEachafterEach 保证每个测试在独立环境中运行,是测试隔离的核心手段:

javascript
let 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:

javascript
describe('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 ---'); }); }); });

执行顺序输出:

shell
Outer 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); });

如果异步钩子超时,可以设置自定义超时时间:

javascript
beforeAll(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,确保资源不泄漏
  • 避免在钩子间传递状态,每个测试应能独立运行
标签:Jest