В процессе работы с React Testing Library важно придерживаться определённых конвенций, чтобы код тестов был понятен, поддерживаем и совместим с остальной частью проекта. Соблюдение конвенций помогает минимизировать количество ошибок, улучшить читаемость тестов и упростить процесс совместной работы в команде. В этой части рассматриваются основные принципы и рекомендации по организации тестов в проекте с использованием React Testing Library.
Каждый компонент и функция должны иметь свой собственный файл с тестами, который расположён в том же каталоге, что и сам компонент. Это помогает поддерживать структуру проекта в порядке и легко искать тесты, соответствующие конкретному файлу.
Пример:
/src
/components
/Button
Button.js
Button.test.js
/Header
Header.js
Header.test.js
Файл с тестами должен иметь суффикс .test.js, чтобы было
понятно, что это тесты для данного компонента. Это правило помогает
быстро ориентироваться в проекте и избегать путаницы между исходным
кодом и тестами.
Названия тестов должны быть информативными и чётко описывать
ожидаемое поведение компонента. Рекомендуется использовать формат
it или test,
что позволяет выразить намерения теста в контексте бизнес-логики, а не
технических аспектов.
Пример правильного описания тестов:
it('должен рендерить кнопку с текстом "Отправить"', () => {
render(<Button label="Отправить" />);
expect(screen.getByRole('button')).toHaveTextContent('Отправить');
});
Использование фраз вроде “проверяет, что компонент”, “проверяет, что функция”, “отображает”, “вызывает” помогает чётко указывать, что именно тестируется. Это улучшает понимание теста не только для автора, но и для других членов команды, особенно если тесты будут проверяться в будущем.
render и других утилитrender — это основная утилита, которая
используется для рендеринга компонента в тестах. React Testing Library
предоставляет функцию render, которая позволяет отобразить
компонент в виртуальном DOM и предоставляет API для взаимодействия с
ним, например, с помощью screen.
render(<Button label="Отправить" />);
Рекомендуется не использовать
shallow render или прямое манипулирование внутренним
состоянием компонента, поскольку это нарушает идеологию React Testing
Library, которая ориентирована на тестирование компонентов с точки
зрения пользователя. Тесты должны работать с компонентами так, как они
будут использоваться в реальной жизни, через их публичные интерфейсы и
визуальные элементы, а не через скрытые или внутренние детали
реализации.
Для взаимодействия с компонентами важно использовать методы,
предоставляемые React Testing Library. Это getBy,
findBy, queryBy, а также их вариации,
такие как getByText, getByRole,
getByLabelText и так далее.
Пример:
// Поиск элемента по тексту
const button = screen.getByText('Отправить');
// Поиск элемента по роли
const button = screen.getByRole('button', { name: /отправить/i });
Использование этих методов помогает тестировать компоненты с точки зрения пользователя, что является основной целью React Testing Library.
Если компонент зависит от асинхронных данных (например, делает запрос
к серверу или ожидает загрузки данных), следует использовать асинхронные
методы findBy и waitFor. Эти методы помогают
обеспечить корректное выполнение теста, ожидая появления нужного
элемента.
Пример:
it('должен отобразить список пользователей после загрузки данных', async () => {
render(<UserList />);
await waitFor(() => screen.getByText('Загрузка...'));
const userItems = await screen.findAllByRole('listitem');
expect(userItems).toHaveLength(3);
});
Использование асинхронных методов помогает гарантировать, что тесты будут работать корректно даже при задержках или асинхронных обновлениях состояния.
Тесты должны быть изолированными и независимыми. Это
означает, что каждый тест должен работать независимо от других.
Рекомендуется использовать метод beforeEach или
afterEach для подготовки и очистки состояния перед или
после каждого теста.
Пример:
beforeEach(() => {
// Настройка состояния перед каждым тестом
});
afterEach(() => {
// Очистка ресурсов после каждого теста
});
Эти методы помогают избежать влияния одного теста на другой и поддерживать чистоту и стабильность тестового набора.
Использование моков и шпионов должно быть минимизировано, поскольку это нарушает принцип тестирования с точки зрения пользователя. Тем не менее, если компонент зависит от внешних сервисов или библиотек, моки могут быть полезными для тестирования.
Для создания моков рекомендуется использовать jest.fn() или jest.mock().
Пример:
jest.mock('axios');
it('должен загрузить данные с сервера', async () => {
axios.get.mockResolvedValue({ data: ['user1', 'user2'] });
render(<UserList />);
await waitFor(() => screen.getByText('user1'));
expect(screen.getByText('user2')).toBeInTheDocument();
});
Моки помогают создать искусственные зависимости, позволяя тестировать компонент в условиях, приближённых к реальным, без необходимости взаимодействовать с настоящими внешними сервисами.
React Testing Library поощряет использование методов взаимодействия, основанных на доступности и ролях элементов, а не на внутренних деталях реализации, таких как классы CSS. Слишком тесная привязка тестов к специфичным классам или стилям может привести к хрупкости тестов, если структура компонентов изменится.
Тем не менее, если необходимо проверить стилизацию, можно
использовать методы, такие как toHaveStyle.
Пример:
it('должен иметь правильный цвет фона', () => {
render(<Button color="red" />);
const button = screen.getByRole('button');
expect(button).toHaveStyle('background-color: red');
});
Использование таких проверок должно быть ограничено и исключительно в тех случаях, когда это действительно необходимо.
Важно помнить, что тесты — это не статичный код, и они требуют того же подхода к рефакторингу, что и основной код проекта. Когда код компонента меняется, тесты тоже должны быть обновлены, чтобы отражать новые условия.
Кроме того, нужно поддерживать тесты в актуальном состоянии, устраняя дублирование, улучшая их структуру и уменьшая сложность. Один из методов рефакторинга тестов — это выделение повторяющихся частей в отдельные вспомогательные функции.
Пример:
function setup() {
render(<Button label="Отправить" />);
return screen.getByRole('button');
}
it('должен рендерить кнопку', () => {
const button = setup();
expect(button).toBeInTheDocument();
});
Такой подход помогает сократить дублирование кода и улучшить поддержку тестов в проекте.
Конвенции при написании тестов с использованием React Testing Library позволяют создавать код, который легко поддерживать и расширять. Правильное оформление тестов, использование доступных методов взаимодействия с компонентами, соблюдение изоляции тестов и минимизация использования моков и шпионов — это основные принципы, которые должны быть частью каждого проекта. Чистые и понятные тесты не только повышают качество кода, но и помогают команде быстрее находить и исправлять ошибки.