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