Подготовка и очистка
Часто при написании тестов вам нужно проделать некоторую работу до того, как запустится тест, и некоторую работу по его завершению. Jest предоставляет вспомогательные функции для этих целей.
Repeating Setup
If you have some work you need to do repeatedly for many tests, you can use beforeEach
and afterEach
hooks.
К примеру, допустим, что несколько тестов взаимодействуют с базой городов. У вас есть метод 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();
});
beforeEach
and afterEach
могут работать с асинхронным кодом также, как это делают асинхронные тесты - они могут либо принимать функцию done
в качестве параметра, либо возвращать promise. К примеру, if initializeCityDatabase()
возвращает promise, который вызывает resolve, когда база данных инициализирована, нам бы хотелось вернуть этот promise:
beforeEach(() => {
return initializeCityDatabase();
});
Единовременная настройка
В некоторых случаях, подготовительные работы нужны единожды в начале файла. Особенно это касается случаев, когда подготовительный код исполняется асинхронно, и вы не можете просто заинлайнить его. Jest provides beforeAll
and afterAll
hooks to handle this situation.
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();
});
Определение контекста
По умолчанию, блоки before
и after
применяются к каждому тесту в файле. Можно группировать тесты вместе, используя блок describe
. Находясь внутри блока describe
, блоки beforeAll
и afterAll
применяются только к тестам в этом describe
.
К примеру, допустим у нас есть не только база городов, но и база продовольствия. Мы могли бы организовать различную подготовку к разным тестам:
// Применяется ко всем тестам в этом файле
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', () => {
// Применяется только к тестам в этом describe блоке
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
, находящийся уровнем выше, выполнится до beforeEach
, находящегося внутри describe
блока. Пример ниже иллюстрирует последовательность выполнения всех блоков (хуков).
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 выполняет все обработчики describe
внутри одного файла до того, как будет запущен какой-либо тест. 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.
Рассмотрим следующий пример тестового файла и результат его выполнения:
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
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
note
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'));
// ...
Общие рекомендации
Если тест падает, в первую очередь нужно проверить, что он падает, будучи запущенным в одиночку. В 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
для записи логов выполнения.