Тестирование компонентов React является важной частью обеспечения качества кода. Существует множество способов тестирования различных частей приложения, но определённые паттерны в тестах помогают создавать более надёжные и читаемые тесты. Ниже приводятся примеры различных тестовых паттернов, которые часто встречаются при работе с React Testing Library.
Первым шагом в любом тесте является рендеринг компонента. Это основной паттерн для всех тестов, так как перед тем, как проверить логику или взаимодействие, нужно убедиться, что компонент корректно отобразился на странице.
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('должен рендерить компонент без ошибок', () => {
render(<MyComponent />);
const element = screen.getByText(/Hello, world!/i);
expect(element).toBeInTheDocument();
});
Этот тест проверяет, что компонент MyComponent
рендерится корректно и отображает текст “Hello, world!”.
Один из базовых паттернов — проверка того, что в DOM есть необходимые элементы. Например, можно убедиться, что определённые элементы отображаются после рендеринга компонента.
test('должен рендерить заголовок и кнопку', () => {
render(<MyComponent />);
expect(screen.getByRole('heading', { name: /Добро пожаловать/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Нажми меня/i })).toBeInTheDocument();
});
Здесь проверяется наличие заголовка и кнопки с соответствующими
текстами. Использование getByRole позволяет проверять
элементы по их роли, что делает тест более устойчивым и
декларативным.
React Testing Library ориентирована на тестирование пользовательских
взаимодействий, и одним из популярных паттернов является симуляция этих
взаимодействий. Для этого используется метод fireEvent.
import userEvent from '@testing-library/user-event';
test('должен менять текст при нажатии кнопки', () => {
render(<MyComponent />);
const button = screen.getByRole('button', { name: /Нажми меня/i });
userEvent.click(button);
expect(screen.getByText(/Кнопка нажата/i)).toBeInTheDocument();
});
В этом примере с помощью userEvent.click симулируется
клик по кнопке, а затем проверяется, изменился ли текст в
компоненте.
При тестировании асинхронных операций важно правильно ожидать
результаты. React Testing Library предоставляет функции, такие как
findBy, чтобы обрабатывать асинхронные действия в
тестах.
test('должен показать данные после загрузки', async () => {
render(<MyComponent />);
const data = await screen.findByText(/Загрузка завершена/i);
expect(data).toBeInTheDocument();
});
В данном примере компонент загружает данные асинхронно. С помощью
findByText ожидается, что текст “Загрузка завершена”
появится в DOM, когда асинхронная операция завершится.
Иногда компоненты зависят от внешних данных или функций, которые нужно замокировать для корректного тестирования. Это позволяет изолировать компонент и избежать зависимости от внешних источников.
jest.mock('./api', () => ({
fetchData: jest.fn().mockResolvedValue('данные получены'),
}));
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
import { fetchData } from './api';
test('должен отобразить данные после загрузки', async () => {
render(<MyComponent />);
const data = await screen.findByText(/данные получены/i);
expect(data).toBeInTheDocument();
});
Здесь мы мокируем функцию fetchData и заставляем её
возвращать обещание с данными. Это позволяет тестировать компонент без
реальной загрузки данных.
Если компонент использует useEffect для выполнения
побочных эффектов, можно проверить его поведение, ожидая изменения
состояния или появления новых элементов в DOM.
import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
test('должен обновить текст через 2 секунды', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText(/Текст обновлён/i));
expect(screen.getByText(/Текст обновлён/i)).toBeInTheDocument();
});
В данном случае waitFor используется для ожидания
изменения текста после выполнения побочного эффекта в
useEffect.
Если компонент использует React Context для управления состоянием, можно протестировать его поведение, передавая значения через контекст.
import { render, screen } from '@testing-library/react';
import { MyContextProvider } from './MyContext';
import MyComponent from './MyComponent';
test('должен отображать данные из контекста', () => {
render(
<MyContextProvider value="Тестовые данные">
<MyComponent />
</MyContextProvider>
);
expect(screen.getByText(/Тестовые данные/i)).toBeInTheDocument();
});
Здесь создаётся провайдер контекста с заданным значением, и компонент проверяет, отображаются ли данные из контекста.
Модальные окна могут добавлять сложности в тестирование, особенно если их состояние зависит от пользовательских взаимодействий. Один из паттернов заключается в проверке открытия и закрытия модальных окон.
test('должен открывать и закрывать модальное окно', () => {
render(<MyComponent />);
const openButton = screen.getByRole('button', { name: /Открыть модалку/i });
userEvent.click(openButton);
const modal = screen.getByRole('dialog');
expect(modal).toBeInTheDocument();
const closeButton = screen.getByRole('button', { name: /Закрыть/i });
userEvent.click(closeButton);
expect(modal).not.toBeInTheDocument();
});
Здесь тестируется взаимодействие с кнопками, открывающими и закрывающими модальное окно. Важно проверить оба состояния: открытое и закрытое.
Многие компоненты используют динамические списки, данные для которых могут поступать асинхронно или обновляться в процессе работы. Для таких компонентов важно проверять, корректно ли рендерится и обновляется список.
test('должен корректно отображать список элементов', async () => {
render(<MyComponent />);
const listItem = await screen.findByText(/Первый элемент/i);
expect(listItem).toBeInTheDocument();
});
В данном случае мы ожидаем, что список элементов будет корректно отображён после асинхронной загрузки данных.
Необходимо тестировать не только обычные сценарии, но и ошибочные. Например, если компонент должен отображать сообщение об ошибке, его нужно проверить.
test('должен показывать ошибку при неудачной загрузке данных', async () => {
jest.mock('./api', () => ({
fetchData: jest.fn().mockRejectedValue(new Error('Ошибка загрузки')),
}));
render(<MyComponent />);
const errorMessage = await screen.findByText(/Ошибка загрузки/i);
expect(errorMessage).toBeInTheDocument();
});
Тестирует компонент, который должен отображать ошибку при сбое в
загрузке данных. С помощью mockRejectedValue имитируется
ошибка при вызове асинхронной функции.
Параметризированные тесты полезны, когда необходимо проверить
компонент с несколькими наборами входных данных. Для этого можно
использовать функции вроде test.each.
test.each([
['один', 1],
['два', 2],
['три', 3],
])('должен показывать число %i для текста %s', (text, number) => {
render(<MyComponent text={text} />);
expect(screen.getByText(new RegExp(number, 'i'))).toBeInTheDocument();
});
В этом примере тест проверяет, что для различных текстов компонент отображает правильные числа.
Некоторые компоненты могут использовать внешние библиотеки, такие как библиотека для анимаций. Важно проверять их работу в тестах, особенно если анимации имеют влияние на отображение элементов.
test('должен анимировать элемент при рендере', () => {
render(<MyComponent />);
const animatedElement = screen.getByTestId('animated-element');
expect(animatedElement).toHaveStyle('transform: scale(1)');
});
Этот тест проверяет, что элемент с анимацией отображается с начальным состоянием стиля.
Для тестирования пользовательских хуков можно использовать