Работа с библиотеками: React Hook Form, Formik

Основы тестирования форм в React

Формы в React часто являются центром взаимодействия пользователя с приложением. Тестирование форм включает проверку корректной работы полей ввода, валидации, обработки сабмитов и взаимодействия с асинхронными процессами. React Testing Library (RTL) фокусируется на тестировании поведения, а не реализации, что делает тесты более стабильными и приближенными к реальному использованию приложения.

Ключевые моменты при тестировании форм:

  • Элементы формы должны быть доступны через роли и текст: getByRole, getByLabelText, getByPlaceholderText.
  • События ввода: fireEvent.change, userEvent.type. UserEvent предпочтительнее, так как имитирует поведение пользователя более реалистично.
  • Проверка ошибок валидации: queryByText, findByText. Асинхронные проверки требуют использования async/await с RTL.

React Hook Form

React Hook Form (RHF) использует хуки для управления состоянием формы. Основные компоненты тестирования:

  • register для привязки полей.
  • handleSubmit для обработки сабмита.
  • formState.errors для отображения ошибок валидации.

Пример теста поля ввода и валидации:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FormProvider, useForm } from 'react-hook-form';
import MyForm from './MyForm';

test('отображение ошибки при пустом поле email', async () => {
  render(<MyForm />);

  const submitButton = screen.getByRole('button', { name: /отправить/i });
  await userEvent.click(submitButton);

  expect(await screen.findByText(/email обязателен/i)).toBeInTheDocument();
});

test('ввод валидного email убирает ошибку', async () => {
  render(<MyForm />);

  const emailInput = screen.getByLabelText(/email/i);
  await userEvent.type(emailInput, 'user@example.com');

  const submitButton = screen.getByRole('button', { name: /отправить/i });
  await userEvent.click(submitButton);

  expect(screen.queryByText(/email обязателен/i)).not.toBeInTheDocument();
});

Особенности тестирования RHF:

  • Использование FormProvider и useFormContext позволяет тестировать вложенные компоненты.
  • Для асинхронной валидации (resolver) нужно оборачивать тест в async/await.
  • Проверка ошибок лучше через текст ошибок, а не через внутренние состояния.

Formik

Formik управляет состоянием формы через компонент Formik или хук useFormik. Важные элементы:

  • initialValues задаёт начальные значения.
  • onSubmit обрабатывает сабмит.
  • errors и touched используются для валидации.

Пример теста формы с Formik:

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

test('валидация обязательного поля password', async () => {
  render(<MyFormikForm />);

  const submitButton = screen.getByRole('button', { name: /войти/i });
  await userEvent.click(submitButton);

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

test('правильный ввод вызывает onSubmit', async () => {
  const handleSubmit = jest.fn();
  render(<MyFormikForm onSub mit={handleSubmit} />);

  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/пароль/i);

  await userEvent.type(emailInput, 'test@example.com');
  await userEvent.type(passwordInput, '123456');

  const submitButton = screen.getByRole('button', { name: /войти/i });
  await userEvent.click(submitButton);

  expect(handleSubmit).toHaveBeenCalledWith(
    { email: 'test@example.com', password: '123456' },
    expect.any(Object)
  );
});

Особенности тестирования Formik:

  • Ошибки отображаются только для полей, которые были затронуты (touched). При тестировании важно вызвать blur для поля, чтобы активировать ошибку.
  • Асинхронные проверки validationSchema требуют ожидания появления ошибок через findByText.
  • Поддержка formik.setFieldValue упрощает программное изменение значений для сложных компонентов.

Сравнение подходов

Характеристика React Hook Form Formik
Стейт формы Хуки (useForm) Компонент/хук (Formik)
Валидация Синхронная и асинхронная, resolver Yup или функции валидации
Тестирование ошибок Через текст ошибок, formState.errors Через текст ошибок, errors и touched
Имитация пользовательского ввода userEvent.type / fireEvent.change userEvent.type / fireEvent.change
Асинхронные проверки await для сабмита или resolver await для сабмита или validationSchema

Практические советы при тестировании форм

  • Проверять видимые изменения, а не внутреннее состояние компонентов.
  • Использовать screen.getByRole и screen.getByLabelText для поиска элементов.
  • Асинхронные операции всегда тестировать с await findBy... или waitFor.
  • Для динамически изменяемых форм (добавление/удаление полей) использовать queryBy... для проверки отсутствия элементов.
  • Мокировать функции сабмита для проверки правильности передачи данных.

Взаимодействие с библиотеками UI

При использовании UI-библиотек (Material-UI, Ant Design) важно учитывать:

  • Некоторые компоненты не используют стандартные input/textarea, поэтому для тестов нужны селекторы по role или labelText.
  • Асинхронные события (например, открытие меню) требуют waitFor или findBy*.
  • Совместимость с RHF и Formik обеспечивается через специальные контроллеры (Controller в RHF, Field в Formik).

Тестирование форм с React Hook Form и Formik в React Testing Library должно фокусироваться на поведении пользователя: ввод, ошибки, сабмит, а не на внутренней реализации. Такой подход делает тесты устойчивыми к изменениям кода и улучшает качество приложения.