Тестирование интерактивных элементов формы является ключевым аспектом обеспечения корректной работы интерфейса. В React Testing Library акцент делается на тестирование поведения пользователя, а не внутренней реализации компонентов. Это принцип позволяет создавать тесты, которые остаются актуальными при изменении структуры компонентов.
React Testing Library предоставляет несколько методов для поиска элементов на основе реального взаимодействия пользователя:
getByLabelText(label) — ищет элемент по тексту
<label>getByPlaceholderText(placeholder) — ищет элемент по
атрибуту placeholdergetByRole(role, { name }) — ищет элемент по роли,
например textbox, spinbutton для числовых
полейПример:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Form from './Form';
render(<Form />);
const nameInput = screen.getByLabelText(/имя/i);
const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
const emailInput = screen.getByPlaceholderText(/email/i);
Выбор правильного метода поиска критичен для точности теста.
getByLabelText предпочтителен для текстовых полей, так как
он отражает способ взаимодействия пользователя с формой.
Текстовые поля (type="text") являются наиболее часто
используемыми. В тестах важно проверять:
Пример теста:
test('ввод текста в поле имени', async () => {
render(<Form />);
const nameInput = screen.getByLabelText(/имя/i);
await userEvent.type(nameInput, 'Иван Иванов');
expect(nameInput).toHaveValue('Иван Иванов');
});
Важная деталь: userEvent.type симулирует реальный ввод
символ за символом, включая события keydown,
keypress и input. Использование
fireEvent.change менее предпочтительно, так как оно не
полностью отражает поведение пользователя.
Поле type="number" имеет специфику:
min) и максимальные
(max) ограниченияuserEvent.clear() перед набором нового значенияПример:
test('ввод числа в поле возраста', async () => {
render(<Form />);
const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
await userEvent.clear(ageInput);
await userEvent.type(ageInput, '25');
expect(ageInput).toHaveValue(25); // RTL автоматически преобразует строку в число
});
Особенность: проверка значения через toHaveValue
учитывает тип поля, что упрощает валидацию чисел. Также можно
тестировать некорректный ввод:
test('некорректный ввод числа', async () => {
render(<Form />);
const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
await userEvent.type(ageInput, 'abc');
expect(ageInput).toHaveValue(null);
});
Поля type="email" позволяют использовать встроенную
HTML-валидацию. В тестах проверяется:
Пример:
test('ввод корректного email', async () => {
render(<Form />);
const emailInput = screen.getByPlaceholderText(/email/i);
await userEvent.type(emailInput, 'test@example.com');
expect(emailInput).toHaveValue('test@example.com');
});
Для проверки некорректного email можно использовать проверку
валидации формы или свойства validity:
test('ввод некорректного email', async () => {
render(<Form />);
const emailInput = screen.getByPlaceholderText(/email/i);
await userEvent.type(emailInput, 'invalid-email');
expect(emailInput.validity.valid).toBe(false);
});
Использование validity позволяет интегрироваться с
встроенной в браузер валидацией и тестировать её без реализации
кастомной логики.
Для всех типов input важно учитывать следующие события:
focus и blur — проверка поведения при
переходе между полямиchange — реакция на изменение значенияinput — корректное обновление состояния компонентаsubmit — интеграция с кнопкой отправки формыПример с submit:
test('отправка формы с заполненными полями', async () => {
const handleSubmit = jest.fn();
render(<Form onSub mit={handleSubmit} />);
const nameInput = screen.getByLabelText(/имя/i);
const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
const emailInput = screen.getByPlaceholderText(/email/i);
const submitButton = screen.getByRole('button', { name: /отправить/i });
await userEvent.type(nameInput, 'Иван Иванов');
await userEvent.type(ageInput, '30');
await userEvent.type(emailInput, 'ivan@example.com');
await userEvent.click(submitButton);
expect(handleSubmit).toHaveBeenCalledWith({
name: 'Иван Иванов',
age: 30,
email: 'ivan@example.com'
});
});
Тестирование формы через пользовательские события повышает надёжность и обеспечивает соответствие реальному поведению пользователя.
getByRole('spinbutton')
вместо getByLabelText, чтобы тесты учитывали
семантику.toHaveValue('') для
текста и toHaveValue(null) для числа.toHaveValue и validity.valid для максимальной
надёжности.userEvent, а
не fireEvent, чтобы полностью имитировать поведение
пользователя.Такой подход позволяет создавать надёжные и читаемые тесты, которые фокусируются на функциональности, а не на внутренней реализации компонентов, что соответствует философии React Testing Library.