Тестирование React-компонентов: часть первая, теория

Тестирование React-компонентов: часть первая, теория

Как тестировать React-компоненты? Какую библиотеку использовать? Как тестировать компоненты, которые берут данные из Redux, а не из пропсов? Как тестировать компоненты, в которых используется роутинг с помощью react-router-dom? Что делать, если в компоненте есть асинхронный код? Разбираемся вместе с выпускником курса по JavaScript в Elbrus Bootcamp Даниилом Замешаевым.

В первой части этого текста разберемся с инструментами и подходами к тестированию, а во второй — с самим процессом тестирования React-компонентов.

Этот текст основан на личном опыте — однажды на работе меня впервые попросили покрыть тестами React-компонент. У гугле по запросам о Redux и React-router-DOM было много ответов, зачем нужно тестирование, но на тот момент никто подробно не объяснял, как его выполнять. Статьи, которая помогла бы начинающему разработчику на примерах разобраться в этом процессе, не было. Поэтому я решил написать ее сам.

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

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

Пример приложения

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

Тестирование приложения проведем с помощью библиотеки  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-компонент.

⌨️
Хотите изучить основы JS и получать обратную связь? Приходите на бесплатный мастер-класс — зарегистрироваться можно здесь.

Следом идет главная конструкция для теста — функция 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-libraryuserEvent. Он предоставляет набор методов для взаимодействия с элементами DOM, которые максимально приближены к реальному поведению пользователя.

Он может что-то напечатать:

userEvent.type(<Элемент с которым взаимодействует юзер>, «текст который он вводит»)

Может нажать на кнопку:

userEvent.click(<Элемент с которым взаимодействует юзер>)

И выполнить еще ряд действий.

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

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