Динамические формы — один из частых сценариев в React-приложениях, где количество полей зависит от действий пользователя. Тестирование таких форм требует особого внимания, поскольку стандартные методы взаимодействия с DOM должны учитывать изменения структуры компонента во время выполнения.
React Testing Library (RTL) ориентирована на работу с пользовательским интерфейсом, а не с внутренними реализациями компонента. Поэтому все действия строятся вокруг query, get и find функций. Для динамических полей важно:
getBy* для элементов, которые обязательно
должны существовать на момент рендера.findBy* для элементов, появление которых
асинхронное или зависит от действий пользователя.queryBy* для проверки отсутствия элементов
после удаления.Пример поиска динамического поля:
const input = screen.getByPlaceholderText(/Введите имя/i);
Если поле появляется после клика:
const addButton = screen.getByText(/Добавить поле/i);
fireEvent.click(addButton);
const newInput = await screen.findByPlaceholderText(/Новое поле/i);
Процесс добавления полей обычно связан с вызовом события клика или
другого пользовательского действия, которое модифицирует состояние
формы. RTL предоставляет два основных способа имитации таких действий:
fireEvent и userEvent.
Пример теста для добавления поля:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import DynamicForm from './DynamicForm';
test('добавляет новое поле после клика', async () => {
render(<DynamicForm />);
const addButton = screen.getByText(/Добавить поле/i);
await userEvent.click(addButton);
const newInput = await screen.findByPlaceholderText(/Новое поле/i);
expect(newInput).toBeInTheDocument();
});
Ключевой момент: findBy* возвращает промис и
автоматически ожидает появления элемента, что идеально для асинхронных
изменений DOM.
Удаление полей тестируется через имитацию клика по кнопке удаления,
связанной с конкретным элементом. После этого проверяется отсутствие
элемента в DOM с помощью queryBy*:
test('удаляет поле при клике на кнопку удаления', async () => {
render(<DynamicForm />);
const addButton = screen.getByText(/Добавить поле/i);
await userEvent.click(addButton);
const newInput = await screen.findByPlaceholderText(/Новое поле/i);
const removeButton = screen.getByRole('button', { name: /Удалить/i });
await userEvent.click(removeButton);
expect(screen.queryByPlaceholderText(/Новое поле/i)).not.toBeInTheDocument();
});
queryBy* возвращает null, если элемент
отсутствует, что идеально для проверки удаления.
Иногда важно убедиться, что количество полей соответствует ожидаемому после серии действий:
test('добавляет и удаляет несколько полей', async () => {
render(<DynamicForm />);
const addButton = screen.getByText(/Добавить поле/i);
await userEvent.click(addButton);
await userEvent.click(addButton);
let inputs = screen.getAllByPlaceholderText(/Новое поле/i);
expect(inputs).toHaveLength(2);
const removeButtons = screen.getAllByRole('button', { name: /Удалить/i });
await userEvent.click(removeButtons[0]);
inputs = screen.getAllByPlaceholderText(/Новое поле/i);
expect(inputs).toHaveLength(1);
});
Использование getAllBy* позволяет работать с
множественными элементами сразу и проверять их количество.
Динамическое добавление может происходить через API или асинхронные
операции. Для таких случаев RTL предоставляет ожидания через
findBy* и waitFor:
import { waitFor } from '@testing-library/react';
test('добавляет поле после асинхронного запроса', async () => {
render(<DynamicForm />);
const addButton = screen.getByText(/Добавить поле/i);
await userEvent.click(addButton);
await waitFor(() => {
expect(screen.getByPlaceholderText(/Новое поле/i)).toBeInTheDocument();
});
});
waitFor позволяет тесту корректно подождать изменения
DOM, даже если они происходят с задержкой.
getByRole,
getByText, getByPlaceholderText делают тесты
более стабильными.findBy* или waitFor.Для полноценного тестирования формы важно проверять не только динамическое изменение полей, но и успешную отправку данных:
test('отправка формы с динамическими полями', async () => {
const handleSubmit = jest.fn();
render(<DynamicForm onSub mit={handleSubmit} />);
const addButton = screen.getByText(/Добавить поле/i);
await userEvent.click(addButton);
const input = await screen.findByPlaceholderText(/Новое поле/i);
await userEvent.type(input, 'Тестовое значение');
const submitButton = screen.getByText(/Отправить/i);
await userEvent.click(submitButton);
expect(handleSubmit).toHaveBeenCalledWith([{ value: 'Тестовое значение' }]);
});
Это гарантирует, что динамически добавленные поля корректно интегрируются с логикой формы и не теряются при submit.
Динамическое добавление и удаление полей требует тщательного выбора
методов поиска и ожидания элементов. Комбинация getBy*,
findBy*, queryBy* и userEvent
позволяет создавать надежные и читаемые тесты для любых интерактивных
форм.