Название этого поста может немного вводить в заблуждение, так как мы не будем писать моки, но нам нужно использовать некоторые функции, которые предоставляет Jest. Давайте начнем!
Вот пример простого компонента:
Весь код можно найти в этом репозитории GitHub: https://github.com/marekrozmus/blog_mocking_settimeout_with_jest
Это компонент с тремя кнопками, и каждый обработчик кликов содержит setTimeout
использование.
Написание (неправильного) теста может привести к:
- очень быстрый запуск теста, но с неправильными результатами — тест пропускает всю логику
setTimeout
, просто не ждет завершенияsetTimout
- это займет много времени (но не более 5 секунд) — тест ожидает завершения
setTimeout
, но в нашем случае это дольше (10 секунд), чем продолжительность теста по умолчанию, разрешенная в Jest. Мы получим следующую ошибку:
Мы могли бы увеличить время ожидания для модульных тестов, но это не очень хорошая практика. Обычно, когда тест превышает тайм-аут, это означает, что он сломан. Скорее всего, где-то отсутствует async
/ await
или в некоторых вызовах API отсутствуют моки.
Что делать с setTimeout
?
В Jest есть встроенный механизм для обработки таких ситуаций — моки таймера. Когда мы их включаем, мы можем ускорить время вперед внутри теста.
Используйте поддельные таймеры
Нам нужно поместить тестовый код между jest.useFakeTimers()
и jest.useRealTimers()
.
Первый настраивает поддельные таймеры, а второй восстанавливает исходное поведение JS.
Пример 1 — setTimeout вызывает некоторый API
Самый простой случай. Обратный вызов setTimeout
вызывает некоторую функцию.
Мы просто говорим Jest, что все таймеры должны завершиться. После этого мы можем проверить, была ли выполнена логика обратного вызова.
Пример 2 — setTimeout обновляет состояние компонента
В этом случае мы можем столкнуться с ошибкой «не завернуто в действие (…)».
Это связано с тем, что обратный вызов setTimeout
обновляет состояние компонента. Метод Jest, завершающий таймеры, должен быть заключен в act
.
Пример 3 — setTimeout выдает исключение
Метод обратного вызова выдает исключение через 10 секунд. В этом случае нам нужно передать функцию expect
следующим образом:
В приведенном выше примере сначала мы expect
что через 9 секунд не будет исключения. Ожидаем его после окончания таймера.
fireEvent против пользовательского события
Стоит упомянуть еще об одном. Основное отличие в написании теста при использовании fireEvent
или userEvent
для нажатия на кнопку.
При использовании fireEvent
мы получаем синхронный вызов click. Для тестирования setTimeout
с поддельными таймерами не требуется никаких дополнительных настроек.
С userEvent
все иначе. Начиная с версии 14.0.0 API всегда возвращают Promise. Из-за этого нам нужно сделать весь тест async
и ждать нажатия userEvent
.
Другое дело, что нам нужно настроить экземпляр userEvent
, чтобы он правильно работал с поддельными таймерами Jest. Нам нужно настроить его следующим образом:
Заключение
Вот и все. Спасибо за чтение и оставьте мне комментарий, если у вас есть вопрос или предложение по улучшению этого поста.
Проверьте также мои другие посты — удачного тестирования :)
Проверьте также другие мои сообщения о модульном тестировании: