跳至主内容
版本:下一篇

设置与拆卸

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

编写测试时,通常需要在测试运行前进行一些设置工作,并在测试运行后完成一些收尾工作。Jest 提供了辅助函数来处理这些需求。

重复设置

如果有多项测试需要重复执行相同的工作,可以使用 beforeEachafterEach 钩子。

例如,假设多个测试都需要与城市数据库交互。你需要在每个测试前调用 initializeCityDatabase() 方法,并在每个测试后调用 clearCityDatabase() 方法。可以通过以下方式实现:

beforeEach(() => {
initializeCityDatabase();
});

afterEach(() => {
clearCityDatabase();
});

test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});

beforeEachafterEach 处理异步代码的方式与测试处理异步代码相同——它们可以接受 done 参数或返回 Promise。例如,如果 initializeCityDatabase() 返回一个在数据库初始化完成后解析的 Promise,我们可以这样返回该 Promise:

beforeEach(() => {
return initializeCityDatabase();
});

一次性设置

某些情况下,你只需在文件开头执行一次设置。当设置是异步操作时尤其麻烦,因为无法内联完成。Jest 提供了 beforeAllafterAll 钩子来处理这种情况。

例如,如果 initializeCityDatabase()clearCityDatabase() 都返回 Promise,且城市数据库可在测试间复用,我们可以这样修改测试代码:

beforeAll(() => {
return initializeCityDatabase();
});

afterAll(() => {
return clearCityDatabase();
});

test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});

作用域

顶层的 before*after* 钩子适用于文件中的所有测试。在 describe 块内声明的钩子仅作用于该 describe 块内的测试。

例如,假设我们不仅有城市数据库,还有食品数据库。可以为不同测试设置不同的准备逻辑:

// Applies to all tests in this file
beforeEach(() => {
return initializeCityDatabase();
});

test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});

describe('matching cities to foods', () => {
// Applies only to tests in this describe block
beforeEach(() => {
return initializeFoodDatabase();
});

test('Vienna <3 veal', () => {
expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
});

test('San Juan <3 plantains', () => {
expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
});
});

注意顶层的 beforeEach 会在 describe 块内的 beforeEach 之前执行。这有助于理解所有钩子的执行顺序。

beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));

test('', () => console.log('1 - test'));

describe('Scoped / Nested block', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));

test('', () => console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll

执行顺序

Jest 在执行任何实际测试前,会先执行测试文件中所有的 describe 处理函数。这是推荐在 before*after* 处理函数中进行设置/拆卸而非在 describe 块内进行的另一个原因。当所有 describe 块完成后,Jest 默认会按照收集阶段遇到的顺序串行执行所有测试,每个测试完成后都会进行清理再执行下一个。

参考以下示例测试文件和输出:

describe('describe outer', () => {
console.log('describe outer-a');

describe('describe inner 1', () => {
console.log('describe inner 1');

test('test 1', () => console.log('test 1'));
});

console.log('describe outer-b');

test('test 2', () => console.log('test 2'));

describe('describe inner 2', () => {
console.log('describe inner 2');

test('test 3', () => console.log('test 3'));
});

console.log('describe outer-c');
});

// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test 1
// test 2
// test 3

describetest 块类似,Jest 按声明顺序调用 before*after* 钩子。注意外层作用域的 after* 钩子会优先执行。例如,以下是设置和拆卸相互依赖资源的方式:

beforeEach(() => console.log('connection setup'));
beforeEach(() => console.log('database setup'));

afterEach(() => console.log('database teardown'));
afterEach(() => console.log('connection teardown'));

test('test 1', () => console.log('test 1'));

describe('extra', () => {
beforeEach(() => console.log('extra database setup'));
afterEach(() => console.log('extra database teardown'));

test('test 2', () => console.log('test 2'));
});

// connection setup
// database setup
// test 1
// database teardown
// connection teardown

// connection setup
// database setup
// extra database setup
// test 2
// extra database teardown
// database teardown
// connection teardown
备注

如果使用 jasmine2 测试运行器,请注意它会以与声明相反的顺序调用 after* 钩子。为获得相同输出,上述示例应修改如下:

  beforeEach(() => console.log('connection setup'));
+ afterEach(() => console.log('connection teardown'));

beforeEach(() => console.log('database setup'));
+ afterEach(() => console.log('database teardown'));

- afterEach(() => console.log('database teardown'));
- afterEach(() => console.log('connection teardown'));

// ...

通用建议

当测试失败时,首先要检查该测试在单独运行时是否仍然失败。在 Jest 中临时只运行单个测试,可将 test 命令改为 test.only

test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});

test('this test will not run', () => {
expect('A').toBe('A');
});

如果某个测试用例在大型测试套件中运行时频繁失败,但单独运行时却能通过,那么很可能是其他测试用例对其造成了干扰。通常可以通过在 beforeEach 中清除共享状态来解决这个问题。如果不确定共享状态是否被修改,也可以尝试在 beforeEach 中添加数据日志记录。