Skip to main content
Version: 29.7

设置和拆卸

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

¥Often while writing tests you have some setup work that needs to happen before tests run, and you have some finishing work that needs to happen after tests run. Jest provides helper functions to handle this.

重复设置

¥Repeating Setup

如果你需要多次测试重复执行某些工作,则可以使用 beforeEachafterEach 钩子。

¥If you have some work you need to do repeatedly for many tests, you can use beforeEach and afterEach hooks.

例如,假设多个测试与城市数据库进行交互。你有一个必须在每个测试之前调用的方法 initializeCityDatabase(),以及一个必须在每个测试之后调用的方法 clearCityDatabase()。你可以通过以下方式执行此操作:

¥For example, let's say that several tests interact with a database of cities. You have a method initializeCityDatabase() that must be called before each of these tests, and a method clearCityDatabase() that must be called after each of these tests. You can do this with:

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 and afterEach can handle asynchronous code in the same ways that tests can handle asynchronous code - they can either take a done parameter or return a promise. For example, if initializeCityDatabase() returned a promise that resolved when the database was initialized, we would want to return that promise:

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

一次性设置

¥One-Time Setup

在某些情况下,你只需在文件的开头进行一次设置。当设置是异步的时,这可能特别麻烦,因此你无法内联执行此操作。Jest 提供了 beforeAllafterAll 钩子来处理这种情况。

¥In some cases, you only need to do setup once, at the beginning of a file. This can be especially bothersome when the setup is asynchronous, so you can't do it inline. Jest provides beforeAll and afterAll hooks to handle this situation.

例如,如果 initializeCityDatabase()clearCityDatabase() 都返回了 Promise,并且城市数据库可以在测试之间重用,我们可以将测试代码更改为:

¥For example, if both initializeCityDatabase() and clearCityDatabase() returned promises, and the city database could be reused between tests, we could change our test code to:

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

作用域

¥Scoping

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

¥The top level before* and after* hooks apply to every test in a file. The hooks declared inside a describe block apply only to the tests within that describe block.

例如,假设我们不仅有一个城市数据库,还有一个食品数据库。我们可以针对不同的测试进行不同的设置:

¥For example, let's say we had not just a city database, but also a food database. We could do different setup for different tests:

// 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);
});
});

请注意,顶层 beforeEachdescribe 块内的 beforeEach 之前执行。它可能有助于说明所有钩子的执行顺序。

¥Note that the top-level beforeEach is executed before the beforeEach inside the describe block. It may help to illustrate the order of execution of all hooks.

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

执行顺序

¥Order of Execution

Jest 在执行任何实际测试之前会执行测试文件中的所有描述处理程序。这是在 before*after* 处理程序内而不是在 describe 块内进行设置和拆卸的另一个原因。一旦 describe 块完成,默认情况下 Jest 会按照在收集阶段遇到的顺序连续运行所有测试,等待每个测试完成并整理好后再继续。

¥Jest executes all describe handlers in a test file before it executes any of the actual tests. This is another reason to do setup and teardown inside before* and after* handlers rather than inside the describe blocks. Once the describe blocks are complete, by default Jest runs all the tests serially in the order they were encountered in the collection phase, waiting for each to finish and be tidied up before moving on.

考虑以下说明性测试文件和输出:

¥Consider the following illustrative test file and output:

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* 钩子。例如,以下是如何设置和拆除相互依赖的资源:

¥Just like the describe and test blocks Jest calls the before* and after* hooks in the order of declaration. Note that the after* hooks of the enclosing scope are called first. For example, here is how you can set up and tear down resources which depend on each other:

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* 钩子。为了获得相同的输出,上面的例子应该修改如下:

¥If you are using jasmine2 test runner, take into account that it calls the after* hooks in the reverse order of declaration. To have identical output, the above example should be altered like this:

  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'));

// ...

常规建议

¥General Advice

如果测试失败,首先要检查的事情之一应该是当测试是唯一运行的测试时该测试是否失败。要仅使用 Jest 运行一个测试,请暂时将该 test 命令更改为 test.only

¥If a test is failing, one of the first things to check should be whether the test is failing when it's the only test that runs. To run only one test with Jest, temporarily change that test command to a 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

¥If you have a test that often fails when it's run as part of a larger suite, but doesn't fail when you run it alone, it's a good bet that something from a different test is interfering with this one. You can often fix this by clearing some shared state with beforeEach. If you're not sure whether some shared state is being modified, you can also try a beforeEach that logs data.