Стратегия тестирования форм

Тестирование форм в React требует системного подхода, учитывающего пользовательские взаимодействия, валидацию и асинхронные операции. Основная цель — проверить, что форма корректно обрабатывает ввод данных, ошибки и успешную отправку, не привязываясь к внутренней реализации компонентов.


Идентификация элементов формы

Элементы формы должны быть доступны для тестов через публичные селекторы, а не через внутренние классы или идентификаторы. React Testing Library рекомендует ориентироваться на:

  • label textgetByLabelText, queryByLabelText
  • placeholdergetByPlaceholderText
  • rolegetByRole, например, role="button" для кнопок
  • text contentgetByText для кнопок или сообщений об ошибке

Использование этих селекторов обеспечивает устойчивость тестов при изменении структуры DOM.

Пример:

const usernameInput = screen.getByLabelText(/имя пользователя/i);
const submitButton = screen.getByRole('button', { name: /отправить/i });

Симуляция пользовательского ввода

Ввод данных должен воспроизводить действия реального пользователя. Для этого используются методы userEvent из пакета @testing-library/user-event, которые позволяют:

  • вводить текст в поля: userEvent.type(input, 'тест')
  • кликать кнопки: userEvent.click(button)
  • выбирать опции: userEvent.selectOptions(select, 'значение')

Пример заполнения формы:

import userEvent from '@testing-library/user-event';

await userEvent.type(usernameInput, 'john_doe');
await userEvent.type(passwordInput, 'password123');
await userEvent.click(submitButton);

Важно использовать await, так как большинство действий асинхронные.


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

Формы часто содержат клиентскую и серверную валидацию. Проверка в тестах включает:

  • Существование сообщений об ошибке при пустых или некорректных данных
  • Скрытие сообщений после исправления данных
  • Блокировку отправки формы, если данные неверны

Пример проверки ошибки:

await userEvent.click(submitButton);

expect(screen.getByText(/пароль обязателен/i)).toBeInTheDocument();

После исправления данных:

await userEvent.type(passwordInput, 'password123');
expect(screen.queryByText(/пароль обязателен/i)).not.toBeInTheDocument();

Асинхронная отправка и обработка результатов

Формы часто отправляют данные на сервер. Для тестирования асинхронных процессов используется комбинация async/await и waitFor:

import { waitFor } from '@testing-library/react';

await userEvent.click(submitButton);

await waitFor(() => {
  expect(screen.getByText(/успешно сохранено/i)).toBeInTheDocument();
});

waitFor повторяет проверку до появления элемента, что важно для симуляции реальных задержек и сетевых запросов.


Мокирование сетевых запросов

Тесты форм не должны зависеть от реального сервера. Для этого используются мок-функции:

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ success: true }),
  })
);

Это позволяет проверять поведение формы при различных ответах: успешном сохранении, ошибке сервера или тайм-ауте.


Стратегии тестирования разных сценариев

  1. Позитивный сценарий — пользователь вводит корректные данные, форма отправляется, отображается сообщение об успехе.
  2. Негативные сценарии — пустые поля, неверный формат, ошибки сервера.
  3. Граничные случаи — максимальная длина полей, специальные символы, быстрые повторные клики.

Каждый сценарий проверяет только внешнее поведение, а не внутренние методы компонента.


Поддержка читаемости тестов

  • Разделение на блоки describe по типу тестируемого функционала (валидация, успешная отправка, ошибки сервера)
  • Использование осмысленных имён тестов (it('показывает сообщение об ошибке при пустом пароле'))
  • Минимизация дублирования кода через функции-утилиты для заполнения формы

Пример утилиты:

async function fillLoginForm({ username, password }) {
  const usernameInput = screen.getByLabelText(/имя пользователя/i);
  const passwordInput = screen.getByLabelText(/пароль/i);
  const submitButton = screen.getByRole('button', { name: /войти/i });

  await userEvent.type(usernameInput, username);
  await userEvent.type(passwordInput, password);
  await userEvent.click(submitButton);
}

Итоговая структура теста формы

  1. Получение элементов через селекторы с ориентиром на пользователя.
  2. Симуляция ввода данных и действий пользователя через userEvent.
  3. Проверка валидации, сообщений об ошибках и блокировки отправки.
  4. Мокирование сетевых запросов и тестирование асинхронной отправки.
  5. Проверка успешного результата или обработки ошибок сервера.
  6. Повторение для всех критичных сценариев (позитивных, негативных, граничных).

Такой подход позволяет создавать устойчивые, читаемые и эффективные тесты, обеспечивающие надежность формы в любых условиях использования.