Unit testing — это метод тестирования, при котором отдельные компоненты приложения проверяются изолированно, без влияния внешних зависимостей. В контексте React это означает тестирование отдельных компонентов, функций или хуков с целью убедиться, что они корректно обрабатывают входные данные и возвращают ожидаемый результат.
React Testing Library (RTL) отличается от традиционных инструментов тестирования тем, что фокусируется на поведении пользователя, а не на внутренней реализации компонентов. Основной принцип — тестировать то, что видит и с чем взаимодействует пользователь, а не структуру JSX напрямую.
Для использования RTL необходимо установить несколько пакетов:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest
@testing-library/react — основной пакет для рендеринга
компонентов.@testing-library/jest-dom — набор дополнительных
матчеров для проверки DOM-элементов.jest — тестовый раннер и фреймворк.Конфигурация jest может быть минимальной, так как
большинство проектов на Create React App уже включают предустановленные
настройки.
Основной метод RTL для unit-тестов — render(). Он
принимает компонент и возвращает объект с утилитами для поиска элементов
в DOM:
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('рендеринг кнопки с текстом', () => {
render(<Button label="Нажми меня" />);
const buttonElement = screen.getByText('Нажми меня');
expect(buttonElement).toBeInTheDocument();
});
Ключевые моменты:
screen вместо возвращаемого объекта
позволяет писать более читаемые тесты.getByText ищет элементы по текстовому содержимому, что
имитирует взаимодействие пользователя.RTL предоставляет несколько стратегий поиска элементов:
getByText, getByRole,
getByLabelText — возвращают элемент или выбрасывают ошибку,
если его нет.queryByText, queryByRole — возвращают
элемент или null, полезно для проверок отсутствия.findByText, findByRole — возвращают
промис, используются для асинхронного рендеринга.Пример с ролью:
render(<button>Отправить</button>);
const button = screen.getByRole('button', { name: /отправить/i });
expect(button).toBeEnabled();
Для проверки поведения компонентов важно имитировать действия
пользователя. RTL использует fireEvent и
userEvent (рекомендуется userEvent для более
реалистичных взаимодействий).
import { fireEvent } 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: /увеличить/i });
await userEvent.click(button);
const counter = screen.getByText('1');
expect(counter).toBeInTheDocument();
});
Отличие fireEvent и
userEvent:
fireEvent — низкоуровневая симуляция событий, не
учитывает асинхронность и взаимодействия браузера.userEvent — высокоуровневая симуляция, имитирует
поведение настоящего пользователя, включая ввод текста, клики и
фокус.Многие компоненты работают с асинхронными операциями, например с
fetch или таймерами. Для их тестирования применяются
findBy* и функции ожидания:
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
test('загрузка пользователей', async () => {
render(<UserList />);
const users = await screen.findAllByRole('listitem');
expect(users.length).toBeGreaterThan(0);
});
test('ожидание изменений DOM', async () => {
render(<UserList />);
await waitFor(() => expect(screen.getByText('Иван')).toBeInTheDocument());
});
waitFor позволяет дождаться изменения DOM, полезно при
асинхронных рендерах.findBy* автоматически ждёт появления элемента до
таймаута по умолчанию.RTL не предоставляет прямого доступа к внутреннему состоянию компонентов, что является преимуществом: тесты становятся независимыми от реализации. Проверка происходит через эффекты на DOM:
render(<Toggle />);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).not.toBeChecked();
userEvent.click(checkbox);
expect(checkbox).toBeChecked();
Состояние проверяется через видимый результат — состояние чекбокса, изменение текста или класса.
Unit-тесты должны быть изолированы, поэтому внешние зависимости, такие как API-запросы, компоненты третьих сторон или хуки, часто мокируются:
jest.mock('./api', () => ({
fetchData: jest.fn(() => Promise.resolve(['Пользователь1', 'Пользователь2']))
}));
jest.restoreAllMocks().Структура тестов имеет большое значение для поддерживаемости:
Component.test.js).describe для группировки связанных
тестов:describe('Button', () => {
test('рендерится с правильным текстом', () => {...});
test('вызывает onClick при клике', () => {...});
});
@testing-library/react/cleanup-after-each встроено в
современную версию), что предотвращает влияние предыдущих тестов.userEvent вместо fireEvent
для имитации действий пользователя.Этот подход позволяет создавать надежные unit-тесты для React-компонентов, обеспечивая высокое качество приложения и минимизируя риски ошибок при изменении кода.