Validation сообщения в формах

Валидация форм является критически важным аспектом пользовательского интерфейса. Она обеспечивает корректность вводимых данных, предотвращает ошибки и повышает качество взаимодействия пользователя с приложением. 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-* атрибутов: улучшает семантическую точность и доступность тестов.
  • Минимизация прямой работы со стейтом: проверка должна быть через видимое поведение.

Интеграция с form libraries

Для библиотек управления формами, таких как Formik или React Hook Form, подход остается схожим:

  • Идентификация полей через label или placeholder.
  • Использование fireEvent или userEvent для изменения значений.
  • Проверка сообщений об ошибках через getByRole('alert') или getByText.
  • Для асинхронной валидации применяется findByText.

Итоговые рекомендации по React Testing Library

  • Тесты должны отражать поведение пользователя, а не реализацию.
  • Валидация сообщений проверяется через видимые изменения DOM.
  • Для динамических ошибок использовать queryBy* и findBy*.
  • Структурирование тестов по типу ошибок повышает читаемость и поддерживаемость.

Тщательное тестирование сообщений в формах обеспечивает надёжность пользовательского интерфейса и предотвращает критические ошибки при вводе данных.