React Testing Library (RTL) строится на принципах, которые отличают её от классических подходов к тестированию компонентов в React. Основная цель — проверять поведение интерфейса так, как это делает пользователь, а не внутреннюю реализацию компонентов.
1. Тестирование через поведение, а не через
реализацию Тесты ориентируются на то, как компонент проявляет
себя в DOM, а не на внутренние методы или состояние. Например, вместо
проверки вызова метода setState предпочтительно проверять,
как изменение состояния влияет на отображение интерфейса:
render(<Counter />);
const button = screen.getByText('Increment');
fireEvent.click(button);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
Здесь тест проверяет видимый результат, а не внутренние детали компонента.
2. Минимизация связей с реализацией Избегается тестирование приватных методов, внутренних пропсов и деталей верстки. Тест должен быть устойчив к рефакторингу: изменение структуры компонента не должно ломать проверку поведения.
3. Упор на доступность (Accessibility) React Testing
Library предоставляет API, которое использует доступные селекторы:
getByRole, getByLabelText,
getByPlaceholderText. Это поощряет создание доступных
интерфейсов. Например:
render(<form><label htmlFor="username">Username</label><input id="username" /></form>);
const input = screen.getByLabelText('Username');
expect(input).toBeInTheDocument();
Использование ролей и меток делает тесты более читаемыми и приближенными к действиям реального пользователя.
render() — функция, которая монтирует
компонент в тестовый DOM. Она возвращает объект с методами для
взаимодействия с компонентом:
const { container, getByText } = render(<MyComponent />);
container позволяет использовать стандартные селекторы
DOM, но предпочтение отдаётся screen — глобальному объекту
для поиска элементов.
screen — основной инструмент для поиска
элементов в тестах. Использует методы:
getBy… — выбрасывает ошибку, если элемент не
найден.queryBy… — возвращает null, если элемент
отсутствует.findBy… — асинхронный поиск, возвращает
Promise.fireEvent и userEvent
fireEvent симулирует события напрямую, тогда как
userEvent имитирует поведение пользователя более
естественно:
import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
render(<button onCl ick={() => console.log('clicked')}>Click me</button>);
const button = screen.getByText('Click me');
fireEvent.click(button); // простое событие
await userEvent.click(button); // имитация реального пользователя с задержками и фокусом
Использование userEvent повышает точность тестов и
делает их ближе к реальному поведению.
Интерфейсы часто обновляются асинхронно. RTL предоставляет методы для работы с асинхронностью:
const promise = screen.findByText('Loaded');
await expect(promise).resolves.toBeInTheDocument();
Также доступны утилиты waitFor и
waitForElementToBeRemoved, которые позволяют дождаться
изменений DOM без жёстких задержек. Это повышает стабильность тестов и
предотвращает ложные срабатывания.
Тесты следует структурировать по поведению, а не по компонентам. Хорошая практика — группировать тесты по сценариям:
Использование describe и it/test помогает
структурировать сценарии:
describe('LoginForm', () => {
it('отображает поля ввода', () => { ... });
it('показывает ошибку при пустом вводе', async () => { ... });
});
React Testing Library часто используется вместе с Jest. Jest предоставляет мокирование функций, таймеров и работу с асинхронностью, а RTL фокусируется на DOM и пользовательских сценариях. Например:
test('вызывает callback при клике', () => {
const handleClick = jest.fn();
render(<button onCl ick={handleClick}>Click</button>);
userEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Этот подход позволяет разделять логику тестирования и реализацию компонента, сохраняя устойчивость тестов при рефакторинге.
React Testing Library формирует стиль тестирования, ориентированный на реальное использование интерфейса, что делает тесты надежными, понятными и устойчивыми к изменениям кода.