«Весь код является виновным, пока его невиновность не будет доказана». - анонимный

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

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

Давай получим.

Сначала мы инициализируем новое приложение React. В этой статье я буду использовать TypeScript.

npx create-react-app test-app --template typescript
# or
yarn create react-app test-app --template typescript

Мы также собираемся установить еще одну библиотеку тестирования, чтобы упростить взаимодействие с нашими компонентами так, как это сделал бы пользователь. Библиотека называется @testing-library/user-event , и она упрощает моделирование реальных пользовательских событий, таких как нажатие и ввод текста, в ваших тестовых примерах.

npm install @testing-library/user-event --save-dev
# or
yarn add @testing-library/user-event --dev

Теперь откройте проект в выбранной IDE и в src создайте следующий файл с именем: /components/ComplexForm/ComplexForm.ts

Вставьте приведенный ниже код в файл:

Затем в ваш App.tsx файл, который будет находиться в корне вашей src папки, вы вставите следующий код:

Прежде чем мы продолжим, я объясню, что делает этот компонент.

  1. Мы создаем компонент под названием ComplexForm, который отображает простую HTML-форму для взаимодействия пользователя.
  2. Форма просит пользователя ввести свое имя, фамилию и проверить, старше ли ему 21 год.
  3. Если пользователю больше 21 года, то отображается другой ввод, спрашивающий пользователя, какой ему любимый напиток.
  4. После заполнения формы пользователь может нажать Применить или Отменить. Обе кнопки будут запускать реквизиты обратного вызова, которые передаются в ComplexForm, как вы можете видеть в App.tsx.
  5. Если пользователь нажимает кнопку «Отправить», мы конвертируем элементы формы в JSON для передачи в обратный вызов onSubmit.

Теперь, когда вы понимаете, что делает весь этот код, запустите проект, используя:

$ cd test-app 
$ yarn start

Вы должны увидеть форму, которая выглядит примерно так:

Теперь у нас есть образец компонента, готовый к тестированию, но сначала давайте поговорим немного о React Testing Library.

Почему библиотека тестирования React?

React Testing Library - это библиотека, предназначенная для тестирования компонентов React. Возможно, вы использовали Enzyme в прошлом для тестирования компонентов React. Чем React Testing Library отличается от Enzyme, так это тем, что она отображает ваши тесты с использованием реальных узлов DOM, а не экземпляров компонентов React.

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

Еще одна важная причина, по которой я предпочитаю использовать React Testing Library, - это философия библиотек, которая, по сути, гласит, что ваши тесты должны напоминать то, как ваши пользователи будут взаимодействовать с вашим приложением. Когда один из ваших пользователей использует ваше приложение, они не знают о том, что они взаимодействуют с состоянием и реквизитами. Им все равно, используете ли вы хуки в функциональных компонентах или компоненты более высокого порядка с компонентами классов. Ваши пользователи видят пользовательские интерфейсы (кнопки, вводы, модальные окна и т. Д.), И именно с этим они взаимодействуют.

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

Применение философии

Итак, как философия библиотеки тестирования React применима к нашему примеру компонента и как мы узнаем, что тестировать? Что ж, давайте подумаем, как пользователь будет взаимодействовать с этим компонентом.

Тестовый пример 1

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

Тестовый пример 2

Следующее логическое взаимодействие для пользователя - начать заполнение формы. Итак, пользователь начнет заполнять форму, а затем перейдет к вопросу «Вам как минимум 21 год?» флажок. Если пользователь нажимает кнопку «Да», мы показываем еще одно поле ввода условно, чтобы он мог ввести свой любимый напиток. Это представляет собой отдельную ветвь кода, который нам нужно протестировать.

Обратите внимание на то, что этот тест напрямую не проверяет использование нами useState. Мы проверяем, видит ли наш пользователь правильную информацию, а не то, что внутреннее состояние изменилось на true или false. Мы могли бы реорганизовать логику состояния, чтобы использовать useReducer или любое другое решение для управления состоянием, и тестовый пример никогда не изменился бы.

Тестовые случаи 3 и 4

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

Написание тестов с использованием декларативного программирования

Итак, мы настроили наш тестовый компонент, мы обсудили, что такое React Testing Library, и мы применили философию React Testing Library для создания наших тестовых примеров. Пришло время приступить к написанию тестов!

Вы часто будете видеть, как разработчики пишут такие тесты:

В этом тесте нет ничего принципиально неправильного, и если ваш компонент действительно прост и требует только одного или двух тестов, то это будет прекрасно. Этот тип тестирования становится проблемой, когда ваши компоненты становятся сложными и у вас появляется 5, 10 или 15+ тестовых примеров для одного компонента.

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

А что, если бы наши тесты были декларативными? Что, если вместо написания тестов, которые показывают, что мы делаем, мы вместо этого напишем тест, описывающий намерения пользователя? Сначала это может показаться запутанным, но я приведу вам пример того, что я имею в виду. Приведенный выше тестовый пример можно переписать как таковой с помощью декларативного программирования:

Вам легче читать и понимать этот тест? Вы можете сразу понять, что происходит, просто прочитав разрешенные функции. Мы проверяем, есть ли в документе поля «Имя» и «Фамилия». Затем мы нажимаем "Вам как минимум 21 год?" флажок, а затем мы проверяем, есть ли в документе вход «Любимый напиток».

Этот тест не только более читабелен, но и вспомогательные средства тестирования, экспортированные из renderComplexForm функции , могут быть повторно использованы в других тестовых примерах. Итак, если бы у нас было всего 10 или 20 тестовых примеров, нам пришлось бы писать и повторять гораздо меньше кода и добиться гораздо большей читабельности. Если бы другой разработчик должен был добавить функции к этому компоненту через 6 месяцев и обновить тесты, им было бы намного проще обновить тесты.

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

Написание тестов для компонента ComplexForm

Наконец, давайте применим эту методологию тестирования к нашему ComplexForm компоненту и напишем фактическую спецификацию теста для 4 тестовых примеров, которые мы задокументировали выше. Вот окончательный код:

Давайте немного разберемся:

  1. Сначала мы создаем нашу функцию рендеринга для нашего компонента. Функция рендеринга отвечает за рендеринг нашего компонента с использованием библиотеки тестирования React и за экспорт вспомогательных функций для наших тестовых случаев. Вы также можете создать отдельный файл для функции рендеринга и импортировать его в свой тест.
  2. В каждом тестовом примере мы вызываем renderComplexForm и получаем вспомогательные функции, которые нам нужны для этого конкретного тестового примера.
  3. Мы создали тестовые вспомогательные функции для изменения входных значений, таких как changeFirstName. Это имитирует взаимодействие пользователя и делает очевидным то, что происходит в тесте.
  4. Функция renderComplexForm принимает аргумент props. Часто ваши компоненты могут принимать реквизиты, которые изменяют пользовательский интерфейс вашего компонента или то, что видит пользователь. Допуская прохождение каждого тестового примера в props, мы также можем тестировать различные взаимодействия в каждом тестовом примере.
  5. Мы используем шутливые имитационные функции для реквизитов onSubmit и onCancel. Мок-функции Jest отлично подходят для проверки того, вызывалась ли функция, сколько раз она вызывалась и с какими аргументами вызывалась. В последних двух тестовых случаях использовались фиктивные функции Jest, чтобы проверить, что нажатие кнопки пользователя запускает соответствующие функции обратного вызова.

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

Дайте мне знать, что вы все думаете, в комментариях!

Вы можете найти репозиторий GitHub по адресу: https://github.com/jerrywithaz/how-to-test-react-app