Тестирование React-компонентов: часть первая, теория
Как тестировать React-компоненты? Какую библиотеку использовать? Как тестировать компоненты, которые берут данные из Redux, а не из пропсов? Как тестировать компоненты, в которых используется роутинг с помощью react-router-dom? Что делать, если в компоненте есть асинхронный код? Разбираемся вместе с выпускником курса по JavaScript в Elbrus Bootcamp Даниилом Замешаевым.
В первой части этого текста разберемся с инструментами и подходами к тестированию, а во второй — с самим процессом тестирования React-компонентов.
Этот текст основан на личном опыте — однажды на работе меня впервые попросили покрыть тестами React-компонент. У гугле по запросам о Redux и React-router-DOM было много ответов, зачем нужно тестирование, но на тот момент никто подробно не объяснял, как его выполнять. Статьи, которая помогла бы начинающему разработчику на примерах разобраться в этом процессе, не было. Поэтому я решил написать ее сам.
Статья предназначена для студентов, которые уже закончили Elbrus Bootcamp, ищут или уже нашли первую работу и столкнулись с необходимостью написать тесты.
Пример приложения
В качестве примера возьмем простое приложение по переводу денег. В нем реализованы страница приветствия, навигационное меню, форма перевода денег с одного кошелька на другой (с возможностью менять кошельки, выводом ошибок, курсами и комиссией), а также страница с уведомлением об успешном переводе денег.
Тестирование приложения проведем с помощью библиотеки react-testing-library — фреймворка Testing Library, которая для React по умолчанию использует в своей основе Jest. Все что будет описано ниже, относится именно к этой библиотеке. А код написан на TypeScript.
Синтаксис React-testing-library
Прежде, чем приступать к тестированию компонентов приложения, разберемся с некоторыми элементами синтаксиса библиотеки:
import Send from './index';
test('From element: exist in the DOM', () => {
render(<Send />)
expect(screen.getByLabelText<HTMLSelectElement>('From')).toBeInTheDocument();
});
Если описывать содержание кода кратко, то после рендера компонента на странице отображается элемент с лейблом «From».
Test — это функция, которая принимает два аргумента: название теста и callback-функцию с логикой. Внутри callback необходимо вызвать функцию render, которая импортируется из библиотеки и принимает тестируемый React-компонент.
Следом идет главная конструкция для теста — функция expect, которую можно описать так:
expect(<реальное состояние>).toBe(<ожидаемое состояние>);
В данном случае нам необходимо убедиться, что выпадающее меню с кошельками <реальное состояние> было отрисовано в HTML-разметке <ожидаемое состояние>.
Получить этот элемент разметки можно с помощью методов объекта screen, который можно импортировали из библиотеки. Он содержит различные методы для взаимодействия с DOM-деревом. В данном случае мы вызываем метод «getByLabelText», который осуществляет поиск элемента, который ассоциирован с тегом <label>, в котором есть текст «From».
Выполнив поиск элемента, дописываем логическое выражение. В данном случае — вызовом функции .toBeInTheDocument().
Как работать с DOM-деревом
У react-testing-library есть две основные особенности: она позволяет абстрагироваться от логики внутри компонента и отслеживает состояние элементов реального DOM. Другими словами, библиотека старается имитировать поведения пользователя и использовать только то, что он увидит в окне браузера.
В примере теста выше можно увидеть, что библиотека ищет элемент в реальном DOM-дереве и исследует его состояние. В данном случае — проверяет, что элемент существует.
А что насчет пункта с абстрагированием от логики внутри компонента? Есть функция расчета комиссии за перевод. Можно предположить, что расчет комиссии содержит логику, в react-testing-library можно проверить только то, что после расчета комиссии в DOM-дереве произошли ожидаемые изменения. Другими словами, мы не проверяем, что state компонента изменился, как в случае с другими библиотеками, — а смотрим, что сумма комиссии на страничке — то, что видит пользователь — соответствует ожиданиям. Внутри компонента логика может быть любой.
Известно, что комиссия равна 1% от суммы перевода:
import Send from './index';
test('Fee element: value was changed after change of amount value', () => {
render(<Send />)
userEvent.type(screen.getByLabelText<HTMLInputElement>('Amount'), '1')
expect(screen.getByLabelText<HTMLInputElement>('Fee').value).toBe('0.01');
})
Код выше выполняет следующее действие: после того, как пользователь ввел значение «1» в поле ввода с лейблом «Amount», поле ввода с лейблом «Fee» будет равно 0.01.
В примере использовался еще один объект из react-testing-library — userEvent. Он предоставляет набор методов для взаимодействия с элементами DOM, которые максимально приближены к реальному поведению пользователя.
Он может что-то напечатать:
userEvent.type(<Элемент с которым взаимодействует юзер>, «текст который он вводит»)
Может нажать на кнопку:
userEvent.click(<Элемент с которым взаимодействует юзер>)
И выполнить еще ряд действий.
Для успешной работы с библиотекой нужно перестать быть разработчиком, который думает о логике внутри своего кода, и стать обычным юзером, который как-то взаимодействует с тем, что видит на страничке, и получает результат своего взаимодействия.
На этом этапе вопросы базового синтаксиса закончены: можно переходить к тестированию. О нем подробно поговорим в следующей статье.