Testing Asynchronous Code
Асинхронний код є дуже поширеним в JavaScript. Коли у вас є код, який працює асинхронно, Jest повинен знати, коли код, що тестується, закінчив свою роботу перед тим, як перейти до наступного тесту. Jest дозволяє це зробити кількома способами.
Зворотні виклики
Найбільш популярним асинхронним паттерном є зворотні виклики.
Наприклад, нехай у вас є функція fetchData(callback)
, яка отримує дані і викликає callback(data)
, коли дані отримано. You want to test that this returned data is the string '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);
});
If done()
is never called, the test will fail (with timeout error), which is what you want to happen.
If the expect
statement fails, it throws an error and done()
is not called. If we want to see in the test log why it failed, we have to wrap expect
in a try
block and pass the error in the catch
block to done
. Otherwise, we end up with an opaque timeout error that doesn't show what value was received by expect(data)
.
Проміси
If your code uses promises, there is a more straightforward way to handle asynchronous tests. Return a promise from your test, and Jest will wait for that promise to resolve. Якщо проміс буде відхилений, тест автоматично впаде.
Наприклад, уявімо, що fetchData
, замість використання зворотніх викликів повертає проміс, який повинен бути вирішений з рядком "peanut butter"
. Ми можемо протестувати це таким чином:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Будьте впевнені що ви повертаєте саме проміс, якщо ви пропустите інструкцію return
, ваш тест завершиться до моменту коли проміс з fetchData
буде вирішен i then() зможе виповнити свій зворотній виклик.
If you expect a promise to be rejected, use the .catch
method. Не забудьте додати expect.assertions
щоб переконатися, що певна кількість перевірок була виконана. Otherwise, a fulfilled promise would not fail the test.
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() зможе виповнити свій зворотній виклик.
If you expect a promise to be rejected, use the .rejects
matcher. Він працює аналогічно матчеру .resolves
. Якщо проміс буде виконано успішно, це викличе помилку в тесті.
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
Async/Await
Окрім того ви можете використовувати async
та await
у ваших тестах. To write an async test, use the async
keyword in front of the function passed to 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');
}
});
You can combine async
and await
with .resolves
or .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.toThrow('error');
});
In these cases, async
and await
are effectively syntactic sugar for the same logic as the promises example uses.
None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style you feel makes your tests simpler.