Есть несколько ловушек, на которые легко попасть, когда дело доходит до асинхронного тестирования. Более того, есть несколько способов добиться того же, в зависимости от вашего вкуса.
Важное понимание, которым может обладать разработчик, - это то, каким плохим методам НЕ следует следовать и выявление шаблонов плохого кода.
В этом посте я хотел бы демистифицировать некоторые из этих асинхронных шаблонов и выделить ловушки, которые скрыты внутри.
Ожидание обещаний
Представьте себе мир без Async / Await.
В следующих случаях использования у нас есть асинхронная функция somethingAsync, которая выполняет некоторую бизнес-логику, которую мы хотим протестировать, и возвращает обещание, возможно, мы также заглушим некоторые из них, но не в этом дело.
Один из шаблонов утверждений может заключаться в вызове этой заглушки или фактического объекта тестирования, и как только обещание разрешается, мы привязываем его к объекту, который позволяет утверждать результат с помощью настраиваемых сопоставителей Jest.
Вот пример:
Дело в том, что вышеприведенный тест написан плохо - он всегда будет проходить.
Поскольку мы не возвращаем обещание из теста, Jest не знает, что этот тест является асинхронным, поэтому он просто вызывает обещание и продолжает работу. Поскольку никаких ожиданий не вызвало никаких ошибок, тест прошел.
Семантику важно понимать:
- Как бы то ни было, когда somethingAsync отклоняет обещание, тест будет пройден, и появится предупреждение о необработанном отклонении обещания, поскольку его не на чем перехватить.
- Если вы измените возвращаемое значение somethingAsync для разрешения вместо отклонения, тогда произойдет нечто еще худшее - ожидаемое значение никогда не достигается, и вы получаете ложное срабатывание без указания того, что test на самом деле ничего не тестирует.
Есть еще один вариант вышеизложенного, и он заключается в том, чтобы обернуть любое обещание ожиданием и использовать его встроенные сопоставители для подтверждения возвращаемого значения.
Это выглядит так:
Также легко попасть в ловушку этой методологии, потому что мы привыкли утверждать структуры данных или, возможно, синхронные вызовы функций.
Таким образом, приведенный выше фрагмент также не работает - пока вызывается ожидание, нет возможности подтвердить его возвращаемое значение, поскольку тестовый код уже завершился.
Результатом приведенного выше фрагмента будет либо прохождение вслепую теста, либо прохождение теста с дополнительным выводом журнала из-за необработанного отклонения обещания и пропущенного ожидания.
Это выглядит примерно так:
Решение для обоих методов, изложенных выше, состоит в том, чтобы вернуть обещание, как показано в следующем примере:
Когда ошибки теряются
Мы вернулись в наш мир Async / Await. Ура!
В следующем варианте использования мы надеемся подтолкнуть приложение к выдаче ошибки и отклонению обещания, и мы хотим поймать его и сопоставить сообщение об ошибке.
Заманчивый подход - перехватить выданную ошибку, поскольку вы знаете, что getUserName будет сгенерировано, и подтвердите точный объект ошибки и сообщение:
Однако здесь есть очень распространенная ошибка, а именно тот факт, что если бы асинхронная функция getUserName () была бы реорганизована таким образом, чтобы фактически разрешить обещание, тогда тест будет проходить вслепую, что сделает этот тест бесполезным и даст ложное срабатывание там, где он должен был не пройти.
Если вам нравится блок try / catch, один из способов решения указанной выше проблемы - объявить ожидаемое количество утверждений следующим образом:
В приведенном выше фрагменте кода есть 2 изменения:
- Мы обновили getUserName () для разрешения, чтобы имитировать рефакторинг кода, который изменил логику.
- Мы добавили ожидаемое количество утверждений в сам тест
Вышеупомянутый тест закончится без каких-либо утверждений из-за того, что блок catch не достигнут. Тогда Jest не пройдёт тест, так как он пропустил ожидаемое количество утверждений.
Явные ожидания
Я считаю, что утверждения считаются несколько не изящными.
К счастью, есть другой способ.
У Jest есть сопоставители обещаний, которые могут утверждать решенное или отклоненное обещание.
Я считаю, что этот способ более явный и самообъясняющий, что тест делает или ожидает.
Давайте изменим приведенный выше тест:
Вы все же должны помнить золотые правила тестирования асинхронного кода - всегда возвращайте обещание (возвращайте ожидаемое) или обязательно дождитесь ожидания, чтобы развернуть возвращаемое значение обещания.
Ожидание утверждений
Последнее замечание о том, когда использовать, а когда избегать планирования утверждений (на основе рекомендации Авы):
Избегайте использования expect.assertions (N) в следующих случаях:
- Ваши тесты синхронны
- Вы используете обещания (в этом случае просто верните ожидание)
- Вы используете async / await с try / catch (опять же, просто дождитесь ожидания)
Используйте expect.assertions (N) в следующих случаях:
- В вашем тесте есть условные выражения - и поэтому в одной ветке кода вы можете иметь одно ожидание, а в другой - несколько. Это делает невозможным планирование точного количества утверждений, но, к счастью, вы можете использовать expect.hasAssertions () для проверки того, что было сделано хотя бы одно утверждение.
- В вашем асинхронном тестовом коде используются обратные вызовы - если вы утверждаете внутри этих обратных вызовов, вы хотите убедиться, что вы определили ожидаемые утверждения, чтобы вы могли быть уверены, что обратный вызов действительно был вызван и утвержден.
Удачи! 😋