Есть несколько ловушек, на которые легко попасть, когда дело доходит до асинхронного тестирования. Более того, есть несколько способов добиться того же, в зависимости от вашего вкуса.

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

В этом посте я хотел бы демистифицировать некоторые из этих асинхронных шаблонов и выделить ловушки, которые скрыты внутри.

Ожидание обещаний

Представьте себе мир без Async / Await.

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

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

Вот пример:

Дело в том, что вышеприведенный тест написан плохо - он всегда будет проходить.

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

Семантику важно понимать:

  • Как бы то ни было, когда somethingAsync отклоняет обещание, тест будет пройден, и появится предупреждение о необработанном отклонении обещания, поскольку его не на чем перехватить.
  • Если вы измените возвращаемое значение somethingAsync для разрешения вместо отклонения, тогда произойдет нечто еще худшее - ожидаемое значение никогда не достигается, и вы получаете ложное срабатывание без указания того, что test на самом деле ничего не тестирует.

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

Это выглядит так:

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

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

Результатом приведенного выше фрагмента будет либо прохождение вслепую теста, либо прохождение теста с дополнительным выводом журнала из-за необработанного отклонения обещания и пропущенного ожидания.

Это выглядит примерно так:

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

Когда ошибки теряются

Мы вернулись в наш мир Async / Await. Ура!

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

Заманчивый подход - перехватить выданную ошибку, поскольку вы знаете, что getUserName будет сгенерировано, и подтвердите точный объект ошибки и сообщение:

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

Если вам нравится блок try / catch, один из способов решения указанной выше проблемы - объявить ожидаемое количество утверждений следующим образом:

В приведенном выше фрагменте кода есть 2 изменения:

  1. Мы обновили getUserName () для разрешения, чтобы имитировать рефакторинг кода, который изменил логику.
  2. Мы добавили ожидаемое количество утверждений в сам тест

Вышеупомянутый тест закончится без каких-либо утверждений из-за того, что блок catch не достигнут. Тогда Jest не пройдёт тест, так как он пропустил ожидаемое количество утверждений.

Явные ожидания

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

У Jest есть сопоставители обещаний, которые могут утверждать решенное или отклоненное обещание.
Я считаю, что этот способ более явный и самообъясняющий, что тест делает или ожидает.

Давайте изменим приведенный выше тест:

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

Ожидание утверждений

Последнее замечание о том, когда использовать, а когда избегать планирования утверждений (на основе рекомендации Авы):

Избегайте использования expect.assertions (N) в следующих случаях:

  1. Ваши тесты синхронны
  2. Вы используете обещания (в этом случае просто верните ожидание)
  3. Вы используете async / await с try / catch (опять же, просто дождитесь ожидания)

Используйте expect.assertions (N) в следующих случаях:

  1. В вашем тесте есть условные выражения - и поэтому в одной ветке кода вы можете иметь одно ожидание, а в другой - несколько. Это делает невозможным планирование точного количества утверждений, но, к счастью, вы можете использовать expect.hasAssertions () для проверки того, что было сделано хотя бы одно утверждение.
  2. В вашем асинхронном тестовом коде используются обратные вызовы - если вы утверждаете внутри этих обратных вызовов, вы хотите убедиться, что вы определили ожидаемые утверждения, чтобы вы могли быть уверены, что обратный вызов действительно был вызван и утвержден.

Удачи! 😋