Многошаговые формы (multi-step forms) часто встречаются в веб-приложениях для постепенного ввода больших объёмов данных. Тестирование таких форм требует особого внимания к состоянию, навигации между шагами и валидации пользовательского ввода. React Testing Library (RTL) предоставляет инструменты для взаимодействия с компонентами так, как это делает пользователь, что делает её идеальным инструментом для таких сценариев.
В многошаговых формах можно выделить несколько ключевых аспектов для тестирования:
Рендеринг и переходы между шагами Необходимо убедиться, что каждый шаг формы отображается корректно и что навигация между шагами работает согласно ожиданиям.
Валидация и обработка ошибок Проверка реакций формы на неверный ввод, корректное отображение сообщений об ошибках и блокировку перехода на следующий шаг.
Сохранение состояния При переходе между шагами введённые данные должны сохраняться, чтобы пользователь не терял введённую информацию.
Финальная отправка данных Проверка корректности агрегированных данных и их передачи на сервер или в хранилище состояния.
В React Testing Library для рендеринга компонента используется
функция render(). Для многошаговой формы важно
протестировать видимость активного шага:
import { render, screen, fireEvent } from '@testing-library/react';
import MultiStepForm from './MultiStepForm';
test('первый шаг отображается при рендере', () => {
render(<MultiStepForm />);
expect(screen.getByText(/Шаг 1: личные данные/i)).toBeInTheDocument();
});
test('переход ко второму шагу по кнопке "Далее"', () => {
render(<MultiStepForm />);
fireEvent.click(screen.getByText(/Далее/i));
expect(screen.getByText(/Шаг 2: адрес/i)).toBeInTheDocument();
});
Важный момент — тест не должен проверять внутреннее состояние компонента напрямую. RTL поощряет проверку через пользовательский опыт: видимые элементы, текст, доступные кнопки.
Многошаговые формы часто включают обязательные поля на каждом шаге. Для тестирования валидации важно проверить, что:
Пример:
test('показывает сообщение об ошибке при пустом имени', () => {
render(<MultiStepForm />);
fireEvent.click(screen.getByText(/Далее/i));
expect(screen.getByText(/Имя обязательно/i)).toBeInTheDocument();
});
Для симуляции пользовательского ввода используется
fireEvent.change или userEvent.type из пакета
@testing-library/user-event:
import userEvent from '@testing-library/user-event';
test('позволяет заполнить имя и перейти к следующему шагу', async () => {
render(<MultiStepForm />);
await userEvent.type(screen.getByLabelText(/Имя/i), 'Иван');
fireEvent.click(screen.getByText(/Далее/i));
expect(screen.getByText(/Шаг 2: адрес/i)).toBeInTheDocument();
});
Для многошаговых форм критично, чтобы введённые данные сохранялись при переходе назад и вперёд. Тесты должны проверять:
Пример:
test('сохраняет данные при переходе назад', async () => {
render(<MultiStepForm />);
await userEvent.type(screen.getByLabelText(/Имя/i), 'Иван');
fireEvent.click(screen.getByText(/Далее/i));
fireEvent.click(screen.getByText(/Назад/i));
expect(screen.getByDisplayValue('Иван')).toBeInTheDocument();
});
После прохождения всех шагов необходимо проверить, что форма
корректно агрегирует данные и вызывает соответствующую функцию отправки
(onSubmit):
test('отправляет все данные при завершении формы', async () => {
const handleSubmit = jest.fn();
render(<MultiStepForm onSub mit={handleSubmit} />);
await userEvent.type(screen.getByLabelText(/Имя/i), 'Иван');
fireEvent.click(screen.getByText(/Далее/i));
await userEvent.type(screen.getByLabelText(/Адрес/i), 'Москва');
fireEvent.click(screen.getByText(/Отправить/i));
expect(handleSubmit).toHaveBeenCalledWith({
name: 'Иван',
address: 'Москва'
});
});
Особое внимание уделяется именно агрегированию данных с разных шагов и их корректной передаче. Проверка должна основываться на реальных действиях пользователя, а не на внутреннем состоянии компонентов.
В многошаговых формах часто есть асинхронная валидация или загрузка
данных (например, проверка email или получение списка городов). RTL
предоставляет waitFor и асинхронные методы
findBy* для корректного тестирования:
import { waitFor } from '@testing-library/react';
test('показывает сообщение о недоступном email', async () => {
render(<MultiStepForm />);
await userEvent.type(screen.getByLabelText(/Email/i), 'test@example.com');
fireEvent.click(screen.getByText(/Далее/i));
await waitFor(() => {
expect(screen.getByText(/Email уже используется/i)).toBeInTheDocument();
});
});
Асинхронные проверки позволяют имитировать реальные задержки и события, возникающие при взаимодействии с API.
describe('Шаг 1', () => { ... }) позволяет локализовать
ошибки.fillStep1(),
fillStep2() упрощает поддержку тестов.screen вместо прямого обращения к
рендеру: Подчеркивает ориентацию на пользовательский опыт.waitFor и асинхронных селекторов.Тщательно спроектированные тесты для многошаговых форм минимизируют риск регрессий при изменениях логики, интерфейса или валидации.