React Testing Library (RTL) — это инструмент для тестирования компонентов React, ориентированный на поведение приложения с точки зрения пользователя, а не на внутренние детали реализации. Он обеспечивает легкость написания тестов, которые остаются актуальными при изменении структуры компонентов.
Для использования RTL необходима установка библиотеки вместе с тестовым раннером. Чаще всего применяют Jest или Vitest:
npm install --save-dev @testing-library/react @testing-library/jest-dom
npm install --save-dev jest
Для Vitest:
npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
После установки важно подключить расширения матчеров
(@testing-library/jest-dom) для удобной проверки
DOM-элементов:
import '@testing-library/jest-dom';
RTL предоставляет набор функций для рендеринга компонентов и поиска элементов на основе их доступности:
render(component) — рендерит React-компонент в
виртуальный DOM для тестирования.
screen — глобальный объект для поиска элементов в
отрендеренном DOM.
Методы поиска:
getByText, getByRole,
getByLabelText — выбрасывают ошибку, если элемент не
найден.queryByText, queryByRole — возвращают
null, если элемент отсутствует.findByText, findByRole — асинхронные,
возвращают Promise, удобно для элементов, которые
появляются с задержкой.Пример рендеринга и проверки текста:
import { render, screen } from '@testing-library/react';
import MyButton from './MyButton';
test('кнопка отображает правильный текст', () => {
render(<MyButton label="Нажми меня" />);
expect(screen.getByText('Нажми меня')).toBeInTheDocument();
});
Для имитации действий пользователя используют
user-event, более реалистичный, чем
fireEvent:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
test('увеличение счётчика по клику', async () => {
render(<Counter />);
const button = screen.getByRole('button', { name: 'Увеличить' });
await userEvent.click(button);
expect(screen.getByText('Счётчик: 1')).toBeInTheDocument();
});
Ключевой момент: user-event эмулирует
реальные события браузера, включая задержки и фокус, что делает тесты
ближе к реальному поведению пользователя.
Многие компоненты работают с асинхронными данными, поэтому необходимо
уметь тестировать отложенное появление элементов. Для
этого применяют findBy* методы:
import { render, screen } from '@testing-library/react';
import AsyncComponent from './AsyncComponent';
test('загрузка данных отображает элемент', async () => {
render(<AsyncComponent />);
const item = await screen.findByText('Данные загружены');
expect(item).toBeInTheDocument();
});
Также можно использовать waitFor, если нужно дождаться
изменения состояния:
import { render, screen, waitFor } from '@testing-library/react';
import AsyncToggle from './AsyncToggle';
test('кнопка меняет текст после загрузки', async () => {
render(<AsyncToggle />);
await waitFor(() => expect(screen.getByText('Готово')).toBeInTheDocument());
});
React Testing Library ориентирован на видимый результат для пользователя, поэтому тесты проверяют текст, роли, атрибуты, а не внутренние методы или стейт напрямую. Например:
expect(screen.getByRole('checkbox')).not.toBeChecked();
expect(screen.getByLabelText('Согласие')).toBeChecked();
Использование toHaveAttribute и toHaveValue
позволяет проверять DOM-атрибуты:
expect(screen.getByRole('textbox')).toHaveValue('Привет');
expect(screen.getByRole('button')).toHaveAttribute('disabled');
Jest:
jest.config.js:module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
jest.setup.js подключение jest-dom:import '@testing-library/jest-dom';
Vitest:
vitest.config.js:import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: './vitest.setup.js',
},
});
vitest.setup.js:import '@testing-library/jest-dom';
RTL позволяет тестировать формы максимально приближенно к пользователю:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';
test('ввод данных в форму и отправка', async () => {
render(<LoginForm />);
await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
await userEvent.type(screen.getByLabelText('Пароль'), '123456');
await userEvent.click(screen.getByRole('button', { name: 'Войти' }));
expect(screen.getByText('Добро пожаловать')).toBeInTheDocument();
});
RTL не рекомендуется проверять внутренние методы или состояние компонента. Тестирование должно опираться на видимые изменения DOM и пользовательский опыт, что повышает устойчивость тестов к рефакторингу.
Пример неправильного подхода:
// Не стоит так тестировать
expect(component.state.count).toBe(1);
Правильный подход:
expect(screen.getByText('Счётчик: 1')).toBeInTheDocument();
screen над деструктурированным возвратом
render — упрощает чтение тестов.getByRole с опцией name для
более точного поиска элементов.findBy*
или waitFor.React Testing Library вместе с Jest или Vitest формирует мощную экосистему для написания надежных, читаемых и поддерживаемых тестов, которые проверяют приложение с точки зрения конечного пользователя, а не внутренней реализации компонентов.