Тестирование форм

Тестирование форм — проверка поведения форм: ввод данных, валидация, отображение ошибок и отправка данных через @testing-library/user-event для симуляции реальных взаимодействий пользователя.

Зачем нужно

Формы — один из самых критичных элементов UI: регистрация, логин, оформление заказа. Ошибки в форме напрямую влияют на конверсию. Тесты форм проверяют валидацию, корректность submit-данных, обработку ошибок сервера и доступность.

Где используется

  • Формы авторизации и регистрации
  • Checkout и payment формы
  • Профили пользователей с редактированием
  • Фильтры и поисковые формы

Основной контент

Базовый тест формы

// LoginForm.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';

test('отправляет форму с корректными данными', async () => {
  const onSubmit = jest.fn;
  const user = userEvent.setup;
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText('Email'), 'alice@example.com');
  await user.type(screen.getByLabelText('Пароль'), 'secret123');
  await user.click(screen.getByRole('button', { name: 'Войти' }));

  expect(onSubmit).toHaveBeenCalledWith({
    email: 'alice@example.com',
    password: 'secret123',
  });
});

Тестирование валидации

test('показывает ошибку при пустом email', async () => {
  const user = userEvent.setup;
  render(<LoginForm onSubmit={jest.fn} />);

  // Отправляем без заполнения
  await user.click(screen.getByRole('button', { name: 'Войти' }));

  expect(screen.getByText('Email обязателен')).toBeInTheDocument;
  expect(screen.getByRole('button')).toBeDisabled; // или форма не отправляется
});

test('показывает ошибку при невалидном email', async () => {
  const user = userEvent.setup;
  render(<LoginForm onSubmit={jest.fn} />);

  await user.type(screen.getByLabelText('Email'), 'not-an-email');
  await user.tab; // blur для запуска валидации

  expect(screen.getByText('Введите корректный email')).toBeInTheDocument;
});

test('не показывает ошибку при валидном email', async () => {
  const user = userEvent.setup;
  render(<LoginForm onSubmit={jest.fn} />);

  await user.type(screen.getByLabelText('Email'), 'alice@example.com');
  await user.tab;

  expect(screen.queryByText('Введите корректный email')).not.toBeInTheDocument;
});

Тестирование react-hook-form

// RegistrationForm.test.tsx — с react-hook-form
test('отображает ошибку при слишком коротком пароле', async () => {
  const user = userEvent.setup;
  render(<RegistrationForm onSubmit={jest.fn} />);

  await user.type(screen.getByLabelText('Пароль'), '123');
  await user.click(screen.getByRole('button', { name: 'Зарегистрироваться' }));

  await screen.findByText('Минимум 8 символов');
});

Тестирование ошибки сервера

test('показывает серверную ошибку при неверных данных', async () => {
  const onSubmit = jest.fn.mockRejectedValue(new Error('Неверный пароль'));
  const user = userEvent.setup;
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText('Email'), 'alice@example.com');
  await user.type(screen.getByLabelText('Пароль'), 'wrongpass');
  await user.click(screen.getByRole('button', { name: 'Войти' }));

  await screen.findByText('Неверный пароль');
});

Тестирование loading-состояния

test('кнопка отключена во время отправки', async () => {
  const onSubmit = jest.fn( => new Promise(() => {})); // зависает
  const user = userEvent.setup;
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText('Email'), 'alice@example.com');
  await user.type(screen.getByLabelText('Пароль'), 'secret');
  await user.click(screen.getByRole('button', { name: 'Войти' }));

  expect(screen.getByRole('button', { name: /Загрузка/ })).toBeDisabled;
});

Частые ошибки

  • fireEvent.change вместо userEvent.typefireEvent не симулирует реальный ввод; react-hook-form и Formik могут не отработать correctly
  • Нет теста на submit без заполнения — граничный случай "пустая форма" часто забывают; это источник багов
  • Тест проверяет HTML-атрибут required — браузерная валидация не работает в jsdom; тестируй кастомную логику валидации компонента

Связанные темы

Ресурсы