Promise.all и Promise.race — это два метода
работы с асинхронными операциями в JavaScript, которые часто
используются для выполнения нескольких асинхронных задач параллельно.
Несмотря на свою популярность, тестирование этих методов требует
некоторых специфических подходов. В этой статье рассматриваются основные
техники и стратегии тестирования этих методов с использованием Jest.
Метод Promise.all принимает массив или иной итерируемый
объект, содержащий несколько промисов, и возвращает новый промис,
который:
При тестировании Promise.all важно учитывать как
успешные сценарии, так и те, где один из промисов отклоняется.
test('Promise.all выполняет все промисы успешно', async () => {
const promise1 = Promise.resolve(3);
const promise2 = Promise.resolve(42);
const promise3 = Promise.resolve('Hello');
await expect(Promise.all([promise1, promise2, promise3])).resolves.toEqual([3, 42, 'Hello']);
});
В данном примере создаются три промиса, которые успешно разрешаются.
Мы используем Jest-метод resolves для проверки того, что
результат выполнения Promise.all соответствует ожидаемому
массиву значений.
test('Promise.all отклоняется при отклонении одного промиса', async () => {
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject(new Error('Failed'));
const promise3 = Promise.resolve('Hello');
await expect(Promise.all([promise1, promise2, promise3])).rejects.toThrow('Failed');
});
Здесь мы проверяем, что если один из промисов отклоняется,
Promise.all также отклонится. Метод rejects
позволяет тестировать отклонения промисов, и в данном случае мы ожидаем
ошибку с сообщением ‘Failed’.
Метод Promise.race работает немного иначе. Он принимает
массив промисов и возвращает новый промис, который:
Важно понимать, что Promise.race не ожидает выполнения
всех промисов, а только того, который завершится первым. Это создает
различные сценарии, которые также следует тестировать.
test('Promise.race выполняется с первым успешным промисом', async () => {
const promise1 = new Promise(resolve => setTimeout(resolve, 100, 'First'));
const promise2 = new Promise(resolve => setTimeout(resolve, 200, 'Second'));
await expect(Promise.race([promise1, promise2])).resolves.toBe('First');
});
В этом тесте Promise.race должен завершиться первым
успешным промисом, даже если второй промис разрешится позже. Тест
подтверждает, что первый результат ‘First’ будет возвращен в результате
выполнения.
test('Promise.race отклоняется с первым отклонённым промисом', async () => {
const promise1 = new Promise((_, reject) => setTimeout(reject, 100, 'Error 1'));
const promise2 = new Promise((_, reject) => setTimeout(reject, 200, 'Error 2'));
await expect(Promise.race([promise1, promise2])).rejects.toBe('Error 1');
});
Здесь Promise.race отклоняется с тем промисом, который
завершится первым. В этом случае это будет отклонение с сообщением
‘Error 1’. Такой тест помогает удостовериться, что метод правильно
обрабатывает сценарии с ошибками.
При работе с асинхронными операциями часто полезно использовать Jest mock-функции, чтобы контролировать поведение промисов и имитировать различные задержки.
test('Promise.all с мок-данными', async () => {
const mock1 = jest.fn(() => Promise.resolve('Mocked 1'));
const mock2 = jest.fn(() => Promise.resolve('Mocked 2'));
const mock3 = jest.fn(() => Promise.resolve('Mocked 3'));
const result = await Promise.all([mock1(), mock2(), mock3()]);
expect(result).toEqual(['Mocked 1', 'Mocked 2', 'Mocked 3']);
});
В данном примере используются мок-функции для имитации асинхронных задач. Это позволяет точно контролировать возвращаемые значения и легче проверять результат работы с промисами.
jest.useFakeTimers();
test('Promise.race с задержками', async () => {
const promise1 = new Promise(resolve => setTimeout(resolve, 100, 'First'));
const promise2 = new Promise(resolve => setTimeout(resolve, 50, 'Second'));
const racePromise = Promise.race([promise1, promise2]);
jest.runAllTimers();
await expect(racePromise).resolves.toBe('Second');
});
В этом примере используется Jest для имитации таймеров. С помощью
jest.useFakeTimers() можно точно контролировать задержки,
чтобы убедиться, что Promise.race завершится с самым
быстрым промисом.
В тестах с асинхронными операциями важно понимать, как Jest
обрабатывает асинхронные ожидания и промисы. При использовании
async/await Jest корректно ожидает завершения всех
промисов, прежде чем продолжит выполнение теста.
Однако при использовании методов .then или
.catch важно явно завершать тест с помощью
done(), если это необходимо, чтобы Jest знал, когда
завершился асинхронный процесс.
done() для завершения теста:test('Promise.all с использованием done', done => {
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
Promise.all([promise1, promise2]).then(result => {
expect(result).toEqual([1, 2]);
done();
});
});
Использование done полезно в тех случаях, когда нужно
контролировать завершение асинхронных операций вручную. Этот подход
особенно полезен в случае, когда тесты используют старые промисы или
когда требуется больше контроля над тестированием.
Порядок выполнения промисов: Тестирование с
Promise.all и Promise.race часто связано с
параллельным выполнением, что означает, что порядок завершения промисов
может быть важен.
Обработка ошибок: Необходимо проверять, как методы работают в случае отклонений. Особое внимание стоит уделить тому, как обрабатываются отклоненные промисы при комбинированных операциях.
Тестирование с задержками: Иногда важно тестировать промисы с имитацией задержек, чтобы проверить правильность работы метода с реальными временными задержками и таймерами.
Таким образом, тестирование асинхронных операций с использованием
Promise.all и Promise.race в Jest требует
внимательности к таким аспектам, как порядок выполнения, ошибки и
тайминги. Применение Jest-методов, таких как resolves,
rejects, а также мок-функций и фейковые таймеры, позволяет
создавать точные и надежные тесты для асинхронных операций в
JavaScript.