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