Название этого поста может немного вводить в заблуждение, так как мы не будем писать моки, но нам нужно использовать некоторые функции, которые предоставляет 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. Нам нужно настроить его следующим образом:

Заключение

Вот и все. Спасибо за чтение и оставьте мне комментарий, если у вас есть вопрос или предложение по улучшению этого поста.

Проверьте также мои другие посты — удачного тестирования :)

Проверьте также другие мои сообщения о модульном тестировании: