Javascript — отличный язык для асинхронного программирования. Но тестировать асинхронный код не всегда легко.

При модульном тестировании рекомендуется попытаться выполнить тестирование на максимально возможном уровне, сосредоточив внимание на тестировании API модуля с точки зрения внешнего использования/использования. Таким образом, мы гарантируем, что он делает то, что должен, но нас не волнует, как именно — практически сводится к нулю работа рефакторинга модуля. когда дело доходит до обновления тестов. Однако это часто увеличивает объем асинхронного тестирования.

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

Первая попытка — с фиктивной задержкой

Обычный шаблон в этих случаях - произвольная задержка после проверенного вызова. В Javascript мы можем сначала определить простую функцию ожидания/задержки как удобную утилиту:

const wait = (ms) => new Promise((r) => setTimeout(r, ms));

А затем используйте это, чтобы «убедиться», что мы получили опубликованное сообщение:

Однако у этого тривиального подхода есть пара проблем:

  • Из-за этого тестирование занимает намного больше времени, чем необходимо (представьте, что выполняются сотни подобных тестов).
  • Он делает предположения о производительности и планировании в среде, в которой выполняются тесты, что позволяет тестам фактически завершаться ошибкой, когда они не должны, в случае неудачного планирования и задержек в выполнении.

Вторая попытка — триггерPromise

Чтобы решить эту проблему, мы создаем служебную функцию, которую мы можем назвать triggerPromise:

Вспомогательная функция возвращает новую функцию Promise вместе с функцией resolve для этого промиса. Давайте воспользуемся им и посмотрим, как он устраняет длительное произвольное ожидание в нашем тесте:

Теперь мы эффективно сигнализируем в точное время, когда мы получили сообщение, а также ожидаем этого сигнала, вместо того, чтобы угадывать и переоценивать.

Завершение всего этого гонкой против тайм-аута

Однако, если в тестируемом модуле есть какая-то ошибка в этой асинхронной функции, то мы не хотим застревать и ждать вечно, потому что это не будет хорошо работать с CI. Таким образом, следующей желаемой утилитой будет что-то вроде ждите этого, но с тайм-аутом. Поскольку у гораздо более используемого Promise.all есть родственный элемент, а именно Promise.race, мы могли бы использовать его следующим образом:

Так что теперь, вместо того, чтобы делать только await promise в наших тестах, мы используем await asyncWithTimeout(promise), и вуаля — CI больше не останавливается из-за новых ошибок в нашем асинхронном поведении!

О да, нам нужен пример использования, завершающий наш небольшой асинхронный тест: