Одноразовые ошибки должны происходить в вашем коде

Разработчики хотят, чтобы ошибки возникали только в одной ситуации, и это в конкретных тестах.

Моя команда разработчиков на работе шутит, что ошибки «это просто функции, о которых пользователи еще не подозревают». 🤪

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

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

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

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

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

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

Вместо того, чтобы встроить все эти проверки в компонент React с помощью кнопки загрузки JSX, мы создали простую вспомогательную функцию JavaScript (метко названную: validateUploadedFile()), которая была импортирована в компонент и взяла на себя большую часть тяжелой работы.

Хотя было очень полезно отделить эту бизнес-логику от компонента, ответственного за инициирование загрузки, было много сценариев потенциальных ошибок, которые нужно было проверить, и успешная проверка правильности ошибок, возникших во время модульного тестирования с помощью Jest, оказалась сложной задачей. Вопреки тому, что вы могли ожидать, здесь не так много примеров или руководств, демонстрирующих, как ожидать возникновения асинхронных ошибок (особенно с кодом, использующим новый синтаксис ES6 async/await).

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

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

Совет: создавайте приложения иначе

Инструменты OSS, такие как Bit, предлагают новую парадигму для создания современных приложений.

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

Это весело, попробуй →

Фреймворк Jest Testing

Jest, если вы не слишком хорошо с ним знаком, - это восхитительный фреймворк для тестирования JavaScript. Он популярен, потому что работает с простым JavaScript и Node.js, всеми основными фреймворками JS (React, Vue, Angular), TypeScript и другими, и его довольно легко настроить в проекте JavaScript.

Хотя начать работу с Jest легко, его «ориентация на простоту» обманчива: jest удовлетворяет так много различных потребностей, что предлагает почти слишком много способов тестирования, и хотя его документация обширна, это не всегда легко для начинающего. средний пользователь Jest (как и я) может найти ответ, который ему / ей нужен, в большом количестве представленных примеров.

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

Компонент для тестирования: ProductFileUpload.js

Ниже представлена ​​очень, очень упрощенная версия компонента React, которая мне нужна для модульного тестирования с помощью Jest. Я расскажу о его назначении под скриншотом кода.

ProductFileUpload.js

Вкратце, компонент позволяет пользователю выбрать файл Excel для загрузки в систему, а функция handleUpload(), прикрепленная к пользовательскому компоненту { UploadFile }, вызывает асинхронную вспомогательную функцию validateUploadedFile(), которая проверяет, соответствуют ли номера продуктов поставляемые товары являются действительными товарами, и если номера магазинов, указанные рядом с этими товарами, являются действительными магазинами. Если все комбинации допустимы, состояние uploadErrors остается пустой строкой, а состояние invalidImportInfo остается нулевым, но если некоторые комбинации недопустимы, оба этих состояния обновляются соответствующей информацией, которая затем запускает сообщения для отображения в предупреждении браузера. пользователь к проблемам, чтобы они могли принять меры для исправления своих ошибок, прежде чем просматривать таблицу, созданную с действительными данными.

Моя задача теперь состояла в том, чтобы провести модульное тестирование: когда validateUploadedFile() выдавал ошибку из-за некоторых недопустимых данных импорта, переданная функция setUploadError() была обновлена ​​новым сообщением об ошибке, а состояние setInvalidImportInfo() было загружено с любыми ошибками, которые были в файле импорта, чтобы пользователи могли увидеть и исправить.

Параметры теста Jest

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

Если вы просто хотите увидеть рабочий тест, переходите к примеру Jest Try / Catch - он, наконец, сработал для меня и моей асинхронной вспомогательной функции.

Что не сработало:

Шутка MockResults()

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

Если после того, как функция validateUploadedFile() вызывается в тесте, функция setUploadedError() имитируется для ответа:

const setUploadError = jest.fn(() => new Error('some product/stores invalid'));

И функция setInvalidImportInfo() вызывается и возвращается с:

const setInvalidImportInfo = jest.fn(() => ({ 
  invalidProducts: ['Product 123456'], 
  invalidStores: ['Store 123']
}));

Согласно шутливой документации, издевательство над плохими результатами функций казалось, что это должно было сработать, но это не так. Вместо этого каждый раз, когда я запускал тест, он просто выдавал сообщение об ошибке "upload error — some records were found invalid” (а не сообщение об ошибке, которого я ожидал) и проваливал тест.

Шутка MockRejectedValue()

Затем я попытался имитировать отклоненное значение самой функции validateUploadedFile(). Похоже, это тоже теоретически должно сработать.

Но, увы, и этот макет не увенчался успехом.

const mockValidateUploadedFile = jest.fn().mockRejectedValue('some product/stores invalid');

И снова ошибка была выдана, и тест не прошел из-за нее.

Шутка SpyOn

Я также попробовал шпионов Джеста. Я импортировал все функции uploadHelper в тестовый файл с использованием подстановочных знаков, а затем настроил шпиона, который будет следить за вызовом validateUploadedFunction() и после его вызова выдавать ожидаемую ошибку.

const mockUploadError = jest.spyOn(uploadHelpers, 'validateUploadedFile')
  .mockImplementation(() => Promise.reject(new Error('some product/stores invalid')));

По-прежнему не повезло. К этому моменту я действительно дошел до конца своей веревки - я не мог понять, что делаю неправильно, и StackOverflow, похоже, тоже не понимал.

Но в конце концов произошел прорыв.

Что сработало:

Шутка Try/Catch с Async/Await

После долгих проб и ошибок и восклицаний почему это не работает?!?!, Был найден ответ, похороненный глубоко в документации Jest среди примеров асинхронности в руководствах.

Вот рабочий тестовый код ниже.

ProductFileUpload.test.js

В конце концов, то, что действительно сработало для меня, заключалось в том, чтобы обернуть тестовую функцию validateUploadedFile() внутри блока try / catch (точно так же, как исходный код компонента, который вызвал эту вспомогательную функцию). Попытка / уловка, окружающая код, была недостающим звеном.

Как только я завернул функцию validateUploadedFile(), высмеял недопустимые данные, которые должны быть переданы в productRows, и высмеял действительные данные, чтобы судить productRows (функции storesService и productService), все встало на свои места.

Были вызваны проверочные макеты, setInvalidImportInfo() макет был вызван с expectedInvalidInfo, а setUploadError() был вызван со строкой, ожидаемой, когда некоторая информация об импорте оказалась бесполезной: "some product/stores invalid".

Наконец-то! 🙌

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

Задача решена.

Заключение

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

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

Повторите попытку через несколько недель - я напишу больше о JavaScript, React, ES6 или чем-то еще, связанном с веб-разработкой.

Спасибо за прочтение. Я надеюсь, что эта статья даст вам лучшее представление о различных способах тестирования асинхронных функций JavaScript с помощью Jest, включая сценарии ошибок, потому что все мы знаем, что они будут происходить вопреки нашим самым лучшим намерениям.

Связанные истории







Ссылки и дополнительные ресурсы: