В JavaScript часто возникают ситуации, когда требуется тестировать асинхронный код, который взаимодействует с микрозадачами (microtasks) и макрозадачами (macrotasks). Jest предоставляет удобные механизмы для тестирования таких операций, и важно понимать, как именно они обрабатываются в рамках цикла событий.
Микрозадачи — это задачи, которые выполняются после
завершения текущего кода, но до того, как будет выполнен следующий цикл
событий. К типичным микрозадачам можно отнести функции, переданные в
Promise.then, Promise.catch или
Promise.finally.
Макрозадачи — это задачи, которые помещаются в
очередь событий и выполняются после завершения текущего цикла событий,
но после выполнения всех микрозадач. К макрозадачам можно отнести
события, такие как setTimeout, setInterval, а
также операции ввода-вывода и обработку событий DOM.
Jest использует цикл событий Node.js для выполнения асинхронного кода, что важно учитывать при тестировании. Для правильного тестирования асинхронных операций необходимо иметь чёткое представление о том, как Jest обрабатывает микрозадачи и макрозадачи.
async и
awaitОдин из самых популярных методов работы с асинхронным кодом —
использование async и await. Jest
предоставляет мощные инструменты для тестирования таких функций.
async/await:test('проверка асинхронной функции', async () => {
const result = await myAsyncFunction();
expect(result).toBe('expected value');
});
В этом примере функция myAsyncFunction возвращает
промис, который резолвится до выполнения утверждения
expect. Jest автоматически ожидает завершения всех
микрозадач, связанных с await, перед выполнением проверок.
Однако важно помнить, что хотя асинхронные задачи могут завершиться
быстро, синхронные части кода (например, утверждения
expect) будут выполнены сразу после того, как вся
асинхронная логика завершится.
Для того чтобы управлять асинхронными тестами в Jest, необходимо
учитывать их порядок исполнения. Это важно, потому что тесты,
использующие микрозадачи, могут завершиться до того, как макрозадачи
(например, setTimeout) будут выполнены.
Jest поддерживает различные способы синхронизации с асинхронным
кодом. Один из ключевых методов — это использование функции
done, которая может быть передана в качестве аргумента в
асинхронные тесты.
test('асинхронная операция с использованием done', (done) => {
setTimeout(() => {
expect(true).toBe(true);
done(); // завершение теста
}, 100);
});
Здесь done сообщает Jest, что тест завершён, и после
выполнения асинхронной операции можно будет продолжить выполнение
тестов.
При тестировании кода, который включает промисы или другие микрозадачи, важно помнить, что Jest может не завершить выполнение теста до того, как промис будет полностью обработан.
test('микрозадачи должны быть завершены до утверждений', async () => {
let value = false;
Promise.resolve().then(() => {
value = true;
});
expect(value).toBe(false); // будет выполнено до завершения микрозадачи
await Promise.resolve();
expect(value).toBe(true); // после выполнения микрозадачи
});
Здесь мы сначала проверяем значение переменной value,
которое ещё не изменилось, так как промис ещё не завершился. После
выполнения await Promise.resolve() мы убеждаемся, что
микрозадача завершилась и значение переменной изменилось.
Для работы с макрозадачами, такими как setTimeout и
setInterval, Jest имеет встроенные механизмы, которые
позволяют точно контролировать выполнение этих операций.
setTimeout:test('макрозадача с использованием setTimeout', () => {
jest.useFakeTimers(); // Использование фейковых таймеров
const callback = jest.fn();
setTimeout(() => {
callback('hello');
}, 1000);
// Перед тем как сработает таймер, callback не был вызван
expect(callback).not.toHaveBeenCalled();
// Прокачиваем фейковые таймеры, имитируя проход времени
jest.advanceTimersByTime(1000);
// Теперь callback должен быть вызван
expect(callback).toHaveBeenCalledWith('hello');
jest.useRealTimers(); // Восстановление реальных таймеров
});
Здесь используется метод jest.useFakeTimers(), который
позволяет имитировать работу таймеров, таких как setTimeout
или setInterval, и точно контролировать их выполнение в
тестах. Функция jest.advanceTimersByTime() используется для
искусственного увеличения времени, что позволяет тестировать макрозадачи
без необходимости реального ожидания.
При тестировании асинхронных операций, которые используют как микрозадачи, так и макрозадачи, важно помнить, что макрозадачи будут выполняться только после завершения всех микрозадач.
test('микрозадачи и макрозадачи', async () => {
const microTask = jest.fn();
const macroTask = jest.fn();
Promise.resolve().then(() => microTask());
setTimeout(() => macroTask(), 0);
await Promise.resolve(); // Завершаем все микрозадачи
// Проверка, что микрозадача была выполнена до макрозадачи
expect(microTask).toHaveBeenCalled();
expect(macroTask).toHaveBeenCalled();
});
В этом примере микрозадача будет выполнена до того, как начнётся выполнение макрозадачи. Это ключевой момент, который необходимо учитывать при тестировании кода, который включает в себя как промисы, так и таймеры.
Важно правильно завершать асинхронные тесты. В Jest это можно сделать несколькими способами:
async/await
— для явного ожидания завершения промисов.done — для синхронизации
завершения тестов с асинхронными операциями.Правильное понимание этих механизмов и грамотное использование инструментов Jest позволяет эффективно тестировать асинхронный код, включая задачи, связанные с микрозадачами и макрозадачами.