Здравствуйте, меня зовут Марк, я фронтенд-разработчик, работающий над стеком React в компании John Lewis. Недавно я участвовал в модернизации подхода к тестированию компонентов React в некоторых наших микроинтерфейсных приложениях.

Введение

Тестирование — неотъемлемая часть современной разработки веб-приложений. Динамичный и интерактивный характер веб-приложений означает, что проверка нашего кода и пути пользователя важнее, чем когда-либо. В John Lewis мы пишем модульные тесты для проверки логики Javascript, обычно с помощью Jest. Мы используем компонентные тесты для тестирования компонентов React более высокого уровня и пишем интеграционные тесты для более сложных пользовательских путей или вариантов использования, как правило, с Cypress.

Enzyme и React Testing Library (RTL) — это пакеты, которые предоставляют утилиты для улучшения модульных тестов для компонентов React. Enzyme был популярен в течение нескольких лет, но React Testing Library, относительный новичок в этом блоке, произвела фурор в экосистеме React. Я собираюсь рассказать о том, почему разработчики переходят на RTL и как команды могут перейти на RTL.

Зачем тестировать компоненты?

Чистая функция Javascript имеет входные данные и выходные данные, что упрощает написание тестовых примеров для ожидаемых входных и выходных данных. По мере того, как мы начинаем взаимодействовать с API-интерфейсами браузера и использовать фреймворки для создания блоков HTML, становится все труднее просто думать с точки зрения ввода/вывода, и мы должны больше думать о том, как компонент ведет себя для пользователя. Тесты компонентов обычно пишутся для соответствия компонентам, написанным в выбранной нами среде (в нашем случае React). Тесты компонентов помогают нам убедиться, что взаимодействие пользователя с компонентами пользовательского интерфейса соответствует поведению, которое мы записали в коде.

Тестирование с помощью фермента

Enzyme может выполнять поверхностный рендеринг (без дочерних компонентов) или полностью рендерить компонент (рендеринг всех дочерних компонентов), он также предоставляет API для взаимодействия с компонентами пользовательского интерфейса и имитации пользовательских событий, таких как нажатия кнопок. Затем разработчики могут использовать ожидания для утверждения поведения компонента, см. полный Enzyme API здесь. Это короткий пример теста, который находит кнопку на основе класса .nav-button, нажимает кнопку и проверяет, была ли вызвана наша функция.

import { shallow } from ‘enzyme’;

test(‘navClickEvent called when NavButton is clicked’, () => {
  const navClickEvent = jest.fn();
  component = shallow(<NavButton navClickEvent={navClickEvent} />);
  component.find(‘.nav-button’).simulate(‘click’);
  expect(navClickEvent).toHaveBeenCalledTimes(1);
});

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

import { shallow } from ‘enzyme’;

test(‘call function via props example’, () => {
  const handleClick = jest.fn();
  const component = shallow(<ExampleComponent handleClick={handleClick}/>);
  component.find(‘ExampleComponent’).props().handleClick();
  expect(handleClick).toBeCalledTimes(1);
});

Сделав это, мы нарушили 2 правила эффективного тестирования компонентов: 1) мы не имитируем поведение реального пользователя и 2) мы обращаемся к внутренним компонентам компонента, чтобы искусственно вызвать функцию handleClick. Поскольку Enzyme раскрывает внутренности компонентов, разработчики часто используют шаблоны тестирования, которые зависят от них. Тесты, которые манипулируют внутренними компонентами или взаимодействуют с ними, часто приводят к:

  1. Тесты, написанные больше с точки зрения разработчика, чем пользователя. Пользователь должен будет взаимодействовать с компонентом через пользовательский интерфейс и не сможет напрямую вызывать функцию, переданную через свойства. Поэтому тест не является реальным представлением того, как компонент будет вести себя в результате взаимодействия с пользователем.
  2. Хрупкие тесты из-за взаимодействия с внутренними методами и свойствами компонентов. Если мы постоянно ссылаемся на конкретные имена свойств и методов в нашем компоненте, мы должны обновлять наши тесты каждый раз, когда мы что-то переименовываем, а не только при изменении логики компонента. В результате тесты требуют большего обслуживания и легко ломаются при изменении или рефакторинге кода.

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

Тестирование с помощью библиотеки тестирования React (RTL)

RTL — это реализация Библиотеки тестирования DOM, которая продвигает запросы DOM для узлов способом, аналогичным тому, как пользователь находит элементы на странице. Подход этой библиотеки состоит в том, чтобы поощрять тестирование, как если бы вы были пользователем, взаимодействуя с DOM, и, следовательно, лучше имитировать реальное использование компонента.

RTL визуализирует компоненты и дочерние компоненты, используя смоделированную среду DOM, называемую JSDOM. В приведенном ниже примере мы отображаем компонент AccountLinks, но пишем тесты только для дерева DOM, которое предоставляется через API библиотеки тестирования React. Мы не запускаем наши тесты на самом компоненте, не имеем доступа к свойствам и не можем напрямую запускать какие-либо функции в компоненте. RTL предоставляет объект экран, который имеет различные методы для опроса DOM.

import { render, screen } from ‘@testing-library/react’;
import userEvent from ‘@testing-library/user-event’;
test(‘account links are displayed when user is signed in’, () => {
  render(<AccountLinks signedIn={true} />);
  userEvent.click(screen.getByText(‘Account’));
  expect(screen.getByRole(‘link’, { name: /my account/i})).toBeInTheDocument();
  expect(screen.getByRole(‘link’, { name: /wish list/i})).toBeInTheDocument();
  expect(screen.getByText(“Sign out”)).toBeInTheDocument();
  expect(screen.getByText(“Sign in”)).not.toBeInTheDocument();
});

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

API библиотеки тестирования React поощряет тестирование DOM с помощью таких методов, как getByText (который ищет текстовую строку в DOM), getByRole (который ищет определенное значение роли HTML) и getByTestID (в идеале, в крайнем случае, выберите элемент на основе «данных». -testid», который мы назначаем элементу HTML). Как и в случае с Enzyme, у нас по-прежнему есть доступ к пользовательским событиям, и мы можем запускать события на странице. В приведенном ниже примере мы используем userEvent.click() для этого и передаем элемент, который хотим щелкнуть.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test(‘calls onClose when clicked’, () => {
  const onClose = jest.fn();
  render(<CloseButton onClose={onClose} />);
  const closeButton = screen.getByRole(‘button’);
  expect(closeButton).toBeInTheDocument();
  userEvent.click(closeButton);
  expect(onClose).toHaveBeenCalledTimes(1);
});

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

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

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

  • RTL способствует пользовательскому взаимодействию в тестах компонентов.
  • Поощряет опрос DOM с использованием текста, ролей и тестовых идентификаторов (например, нет метода RTL для получения по имени класса, поскольку это детали реализации), мы проверяем, что пользователь испытывает при отображении страницы.
  • Продвигает стабильные и значимые тесты, скрывая детали реализации, тогда как Enzyme позволяет получить доступ к внутренним компонентам с помощью таких методов, как props().
  • Синтаксис чистый и понятный.
  • Отличная документация.
  • Поверхностный рендеринг Enzyme не запускает хуки React, такие как useEfffect, поэтому необходимы обходные пути или другие библиотеки, если вы используете функциональный компонент с хуками.
  • Адаптер реакции Enzyme обеспечивает совместимость с React 17, но поддержка будущих версий React выглядит неопределенной.

Если вы все еще не уверены в преимуществах RTL, я бы порекомендовал эту статью создателя RTL Кента Доддса для подробного обзора его преимуществ. Если ваша команда думает о переходе с тестов Enzyme на Testing Library, ознакомьтесь с руководством по миграции здесь.

Как сделать шаг в своей команде:

Вы можете установить и использовать React Testing Library вместе с Enzyme, поэтому, если у вас уже есть набор тестов Enzyme, вы можете легко создавать новые тесты в RTL и сохранять существующие тесты Enzyme. Внесение изменений в ваш инструментарий часто является важным решением, но RTL очень удобен для разработчиков, поэтому лучший способ — просто взяться за взлом и написать несколько тестов с использованием RTL!

Вот некоторые подходы, которые сработали в John Lewis:

  • Примите командную политику, в соответствии с которой любые новые тесты компонентов пишутся исключительно с использованием RTL, а не Enzyme, в большинстве случаев это отличная отправная точка.
  • Когда разработчик затрагивает существующий компонент или часть функциональности, переключите тесты компонентов на RTL, если это целесообразно.
  • Определите области вашего приложения, в которых отсутствуют тесты компонентов, и используйте это как возможность усилить ваши тесты с помощью RTL. Этот подход особенно подходит для приложений с низким охватом тестами и более старых приложений, которые используют устаревшие библиотеки или активно используют моментальное тестирование.
  • Если у вас есть стандартный проект для новых интерфейсных приложений (у нас есть проект NextJS под названием Blueprint), объедините RTL с этим проектом и поощряйте его использование. Таким образом, новые команды будут поощряться к его использованию с первого дня.

Продажа RTL вашей команде

Иногда при переходе на новый инструмент могут возникать противодействия со стороны других членов команды. Отличный способ привлечь других разработчиков — запустить всплеск и преобразовать весь тестовый файл в RTL, если вы можете сделать это еще лучше. Этот файл даст команде возможность обсудить, что им нравится и что их беспокоит. В идеале этот файл должен отражать стандарт тестирования, к которому стремится ваша команда в своем наборе тестов, «золотой стандарт», если хотите. Когда разработчики приступают к своей первой части RTL, у них есть на что ссылаться и понимание того, как команда решила написать свои тесты.

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

Я надеюсь, что вы нашли эту статью полезной, и желаю удачи в переходе на RTL. Джон Льюис набирает Front-end разработчиков для работы над нашим стеком React. Дополнительную информацию см. на нашей странице вакансий инженеров.

Полезные статьи: