Миграция на React Testing Library (RTL) — важный шаг для улучшения качества тестирования React-компонентов. RTL предлагает подход, ориентированный на пользовательские действия, что помогает создавать более реалистичные тесты по сравнению с предыдущими библиотеками, такими как Enzyme. RTL фокусируется на взаимодействиях с компонентами так, как это делает конечный пользователь, а не через внутренние детали реализации.
Использование RTL позволяет повысить надежность тестов, улучшить читаемость кода и упростить его поддержку. Главная цель RTL — избежать привязки тестов к внутренним механизмам компонента, что делает тесты менее уязвимыми при изменениях в коде.
Некоторые из ключевых преимуществ RTL:
setState или прямой
доступ к DOM, становятся излишними.Перед тем как приступить к миграции, необходимо убедиться, что проект уже содержит базовые тесты с использованием старой библиотеки тестирования (например, с Enzyme). Чтобы провести миграцию, потребуется тщательно оценить текущие тесты и идентифицировать те, которые можно адаптировать под React Testing Library.
Установка React Testing Library и зависимостей
Первым шагом в миграции является установка необходимых пакетов. Для этого в проект нужно добавить React Testing Library и @testing-library/jest-dom (для работы с удобными матчерами).
npm install --save-dev @testing-library/react @testing-library/jest-domПодготовка конфигурации Jest
Если в проекте используется Jest (что является стандартом для большинства React-приложений), то важно настроить соответствующие глобальные методы и матчеры. Для этого можно добавить следующую строку в файл конфигурации Jest или в отдельный файл настройки, который будет импортироваться перед каждым тестом.
import '@testing-library/jest-dom/extend-expect';
Это позволит использовать такие утверждения, как
.toBeInTheDocument(), .toHaveTextContent(), и
другие методы, которые значительно упрощают написание тестов.
Обновление тестов: заменяем Enzyme на React Testing Library
Преобразование тестов с Enzyme в RTL включает в себя несколько ключевых изменений, которые касаются способов рендеринга компонентов, взаимодействия с элементами и проверки результатов.
Рендеринг компонентов
В Enzyme для рендеринга компонента использовался метод
shallow, который позволяет тестировать компоненты без их
рендеринга с дочерними компонентами. В React Testing Library для
рендеринга используется функция render, которая рендерит
компонент с его дочерними элементами в DOM.
Пример кода:
Enzyme (до миграции):
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
it('should render correctly', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('h1').text()).toBe('Hello, world!');
});
React Testing Library (после миграции):
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
it('should render correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Hello, world!')).toBeInTheDocument();
});Работа с событиями
В Enzyme для имитации пользовательских событий использовался метод
simulate. В RTL взаимодействие с компонентом осуществляется
с помощью fireEvent или userEvent
(предпочтительнее для симуляции реальных пользовательских действий).
Пример кода:
Enzyme (до миграции):
const wrapper = shallow(<MyComponent />);
wrapper.find('button').simulate('click');
expect(wrapper.state('clicked')).toBe(true);
React Testing Library (после миграции):
import { fireEvent, render } from '@testing-library/react';
import MyComponent from './MyComponent';
it('should update state on button click', () => {
const { getByText } = render(<MyComponent />);
fireEvent.click(getByText('Click me'));
expect(getByText('You clicked!')).toBeInTheDocument();
});Использование пользовательских событий
Для имитации пользовательских взаимодействий предпочтительнее
использовать userEvent, так как эта библиотека более точно
симулирует поведение пользователя, в отличие от fireEvent,
который использует более низкоуровневые события.
Пример:
import userEvent from '@testing-library/user-event';
it('should type in the input field', async () => {
const { getByRole } = render(<MyComponent />);
const input = getByRole('textbox');
await userEvent.type(input, 'Hello');
expect(input).toHaveValue('Hello');
});Тестирование асинхронных операций
React Testing Library активно использует методы для тестирования
асинхронных операций, такие как findBy,
waitFor, и waitForElementToBeRemoved.
Пример:
it('should load data asynchronously', async () => {
const { getByText } = render(<MyComponent />);
await waitFor(() => getByText('Data loaded'));
expect(getByText('Data loaded')).toBeInTheDocument();
});Проверка визуальных изменений
В React Testing Library важно использовать методы, такие как
getByText или getByRole, для поиска элементов
в DOM, а не напрямую проверять изменения в стейте или классы
компонентов. Это помогает делать тесты более ориентированными на
поведение, а не на внутреннюю реализацию.
Пример:
it('should show a loading message while fetching data', () => {
const { getByText } = render(<MyComponent />);
expect(getByText('Loading...')).toBeInTheDocument();
});Миграция тестов с компонентами, использующими контекст
В случае, если компоненты используют контекст (например, React Context API), важно также адаптировать тесты, чтобы они корректно обрабатывали контекстное состояние. Для этого используется компонент-поставщик контекста, оборачивающий тестируемые компоненты.
Пример:
import { MyContextProvider } from './MyContext';
it('should consume context value', () => {
const { getByText } = render(
<MyContextProvider>
<MyComponent />
</MyContextProvider>
);
expect(getByText('Context value: true')).toBeInTheDocument();
});Миграция на React Testing Library может занять некоторое время, но результаты оправдают усилия. Она делает тесты более читаемыми, надежными и приближенными к реальным взаимодействиям пользователей.