Валидация форм является критически важным аспектом пользовательского интерфейса. Она обеспечивает корректность вводимых данных, предотвращает ошибки и повышает качество взаимодействия пользователя с приложением. React Testing Library предоставляет инструменты для проверки поведения компонентов формы и корректного отображения сообщений об ошибках.
Форма обычно состоит из нескольких полей ввода (input,
textarea, select) и кнопки отправки. Сообщения
об ошибках могут отображаться:
Для тестирования важно идентифицировать поля ввода и
сообщения об ошибках через семантические селекторы, такие как
getByLabelText, getByRole,
getByText, а также с использованием aria-*
атрибутов для доступности.
Пример типичной формы:
function LoginForm({ onSubmit }) {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [errors, setErrors] = React.useState({});
const validate = () => {
const newErrors = {};
if (!email.includes('@')) newErrors.email = 'Неверный email';
if (password.length < 6) newErrors.password = 'Пароль слишком короткий';
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length) {
setErrors(validationErrors);
} else {
onSubmit({ email, password });
}
};
return (
<form onSub mit={handleSubmit}>
<label>
Email
<input
value={email}
onCha nge={(e) => setEmail(e.target.value)}
/>
</label>
{errors.email && <span role="alert">{errors.email}</span>}
<label>
Пароль
<input
type="password"
value={password}
onCha nge={(e) => setPassword(e.target.value)}
/>
</label>
{errors.password && <span role="alert">{errors.password}</span>}
<button type="submit">Войти</button>
</form>
);
}
Основная цель тестирования — убедиться, что сообщения об ошибках появляются при некорректном вводе и исчезают при корректном. React Testing Library ориентирована на поведение пользователя, поэтому важно симулировать реальные действия:
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
test('отображает ошибки при некорректном вводе', () => {
render(<LoginForm onSub mit={jest.fn()} />);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'invalidemail' } });
fireEvent.change(screen.getByLabelText(/пароль/i), { target: { value: '123' } });
fireEvent.click(screen.getByText(/войти/i));
expect(screen.getByRole('alert', { name: /неверный email/i })).toBeInTheDocument();
expect(screen.getByRole('alert', { name: /пароль слишком короткий/i })).toBeInTheDocument();
});
Ключевые моменты:
fireEvent.change для изменения значений
полей.getByRole('alert')
обеспечивает доступность.Валидация должна быть динамичной: ошибки должны исчезать после исправления данных.
test('ошибки исчезают после корректного ввода', () => {
render(<LoginForm onSub mit={jest.fn()} />);
const emailInput = screen.getByLabelText(/email/i);
const passwordInput = screen.getByLabelText(/пароль/i);
fireEvent.change(emailInput, { target: { value: 'wrong' } });
fireEvent.change(passwordInput, { target: { value: '123' } });
fireEvent.click(screen.getByText(/войти/i));
fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
fireEvent.change(passwordInput, { target: { value: '123456' } });
fireEvent.click(screen.getByText(/войти/i));
expect(screen.queryByText(/неверный email/i)).not.toBeInTheDocument();
expect(screen.queryByText(/пароль слишком короткий/i)).not.toBeInTheDocument();
});
Особенности:
queryByText используется для проверки отсутствия
элемента.Иногда ошибки отображаются не под полями, а обобщённо для всей формы. В этом случае тестирование должно проверять наличие сообщения после отправки и его удаление после исправления всех данных.
test('показывает сообщение на уровне формы', () => {
render(<LoginForm onSub mit={jest.fn()} />);
fireEvent.click(screen.getByText(/войти/i));
expect(screen.getByText(/пожалуйста, заполните все поля/i)).toBeInTheDocument();
});
Если валидация зависит от API или других асинхронных операций,
следует использовать findBy*:
test('асинхронная проверка email', async () => {
render(<LoginForm onSub mit={jest.fn()} />);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'taken@example.com' } });
fireEvent.click(screen.getByText(/войти/i));
const errorMessage = await screen.findByText(/email уже занят/i);
expect(errorMessage).toBeInTheDocument();
});
findBy* автоматически ожидает появления элемента, что
позволяет корректно тестировать асинхронные сценарии.
role и aria-*
атрибутов: улучшает семантическую точность и доступность
тестов.Для библиотек управления формами, таких как Formik или React Hook Form, подход остается схожим:
label или
placeholder.fireEvent или userEvent для
изменения значений.getByRole('alert')
или getByText.findByText.queryBy* и
findBy*.Тщательное тестирование сообщений в формах обеспечивает надёжность пользовательского интерфейса и предотвращает критические ошибки при вводе данных.