Перейти до основного змісту
Version: 25.x

Тестування асинхронного коду

Асинхронний код є дуже поширеним в JavaScript. Коли у вас є код, який працює асинхронно, Jest повинен знати, коли код, що тестується, закінчив свою роботу перед тим, як перейти до наступного тесту. Jest дозволяє це зробити кількома способами.

Зворотні виклики#

Найбільш популярним асинхронним паттерном є зворотні виклики.

Наприклад, нехай у вас є функція fetchData(callback), яка отримує дані і викликає callback(data), коли дані отримано. Ви хочете перевірити, що отримані дані - це рядок "peanut butter".

За замовчуванням тести в Jest завершуються, коли досягають кінця виконання. Це означає, що даний тест не працює, як задумано:

// Не робіть так!
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});

Проблема в тому, що тест закінчується як тільки виконається код функції fetchData, до того, як буде викликано функцію зворотнього виклику.

Існує альтернативний вигляд тесту, який це виправляє. Замість того, щоб писати тест в функції без аргументів, використайте аргумент done. В такому випадку Jest чекатиме виконання зворотнього виклику done перед тим, як завершити тест.

test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});

Якщо done() не буде викликано, тест впаде через таймаут і це саме те, що вам потрібно.

Якщо виклик expect завершиться невдачею, буде викинуто помилку і done() не викличеться. Якщи ми хочемо побачити причину падіння тесту у виводі, то потрібно огорнути expect в конструкцію try і передати помилку у виклик done всередині блоку catch. В іншому випадку тест завершиться з неявною помилкую таймауту, яка не покаже, яке значення було отримане в expect(data).

Проміси#

Якщо ваш код використовую проміси, то існує простіший шлях писати асинхронні тести. Просто поверніть проміс з вашого тесту і Jest чекатиме, поки він виконається. Якщо проміс буде відхилений, тест автоматично впаде.

Наприклад, нехай fetchData, замість використання функції зворотнього виклику, повертатиме проміс, який повинен виконатися з рядком 'peanut butter'. Ми можемо протестувати це так:

test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});

Будьте впевнені що ви повертаєте саме проміс, якщо ви пропустите інструкцію return, ваш тест завершиться до моменту коли проміс з fetchData буде виконаний i then() зможе виповнити свій зворотній виклик.

Якщо ви очікуєте, що проміс буде відхилено, використовуйте метод .catch. Не забудьте додати expect.assertions щоб переконатися, що певна кількість перевірок була виконана. В іншому випадку, виконаний проміс не викличе помилку теста.

test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});

.resolves / .rejects#

Ви також можете використати матчер .resolves у ваших конструкціях expect. Тоді Jest чекатиме поки проміс буде виконано. Якщо проміс буде відхилений, тест автоматично впаде.

test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});

Будьте впевнені що ви повертаєте твердження — якщо ви пропустите інструкцію return, ваш тест буде завершено до моменту як проміс повернутий з fetchData буде вирішен та then() зможе виповнити свій зворотній виклик.

Якщо ви очікуєте, що проміс буде відхилено, використовуйте матчер .rejects. Він працює аналогічно матчеру .resolves. Якщо проміс буде виконано успішно, це викличе помилку в тесті.

test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});

Async/Await#

Окрім того ви можете використовувати async та await у ваших тестах. Щоб написати асинхронний тест, використовуйте ключове слово async перед початком функції, яка передається в test. Наприклад, та сама функція fetchData може бути протестована так:

test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});

Ви можете комбінувати async і await з .resolves або .rejects.

test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});

В цих випадках async і await виступають як синтаксичний цукор для тієї ж логіки, що використовують приклади з промісами.

Жоден з наведених підходів не є кращим за інші і ви можете змішувати їх і поєднувати їх в своєму проекті або, навіть, в одному файлі. Це цілком залежить від того, який підхід робить тести простішими для написання для вас.