Тестирование input полей: текст, число, email

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


Получение и выбор input элементов

React Testing Library предоставляет несколько методов для поиска элементов на основе реального взаимодействия пользователя:

  • getByLabelText(label) — ищет элемент по тексту <label>
  • getByPlaceholderText(placeholder) — ищет элемент по атрибуту placeholder
  • getByRole(role, { name }) — ищет элемент по роли, например textbox, spinbutton для числовых полей

Пример:

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

render(<Form />);

const nameInput = screen.getByLabelText(/имя/i);
const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
const emailInput = screen.getByPlaceholderText(/email/i);

Выбор правильного метода поиска критичен для точности теста. getByLabelText предпочтителен для текстовых полей, так как он отражает способ взаимодействия пользователя с формой.


Тестирование текстового input

Текстовые поля (type="text") являются наиболее часто используемыми. В тестах важно проверять:

  1. Возможность ввода текста
  2. Корректное отображение введённого значения
  3. Взаимодействие с формой (например, активация кнопки submit)

Пример теста:

test('ввод текста в поле имени', async () => {
  render(<Form />);
  
  const nameInput = screen.getByLabelText(/имя/i);
  await userEvent.type(nameInput, 'Иван Иванов');
  
  expect(nameInput).toHaveValue('Иван Иванов');
});

Важная деталь: userEvent.type симулирует реальный ввод символ за символом, включая события keydown, keypress и input. Использование fireEvent.change менее предпочтительно, так как оно не полностью отражает поведение пользователя.


Тестирование числового input

Поле type="number" имеет специфику:

  • Значение хранится как строка, но браузер контролирует допустимые символы
  • Можно проверять минимальные (min) и максимальные (max) ограничения
  • Для симуляции ввода удобнее использовать userEvent.clear() перед набором нового значения

Пример:

test('ввод числа в поле возраста', async () => {
  render(<Form />);
  
  const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
  await userEvent.clear(ageInput);
  await userEvent.type(ageInput, '25');
  
  expect(ageInput).toHaveValue(25); // RTL автоматически преобразует строку в число
});

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

test('некорректный ввод числа', async () => {
  render(<Form />);
  
  const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
  await userEvent.type(ageInput, 'abc');
  
  expect(ageInput).toHaveValue(null);
});

Тестирование email input

Поля type="email" позволяют использовать встроенную HTML-валидацию. В тестах проверяется:

  • Принимаются корректные email адреса
  • Отбрасываются некорректные
  • Валидация формы срабатывает при submit

Пример:

test('ввод корректного email', async () => {
  render(<Form />);
  
  const emailInput = screen.getByPlaceholderText(/email/i);
  await userEvent.type(emailInput, 'test@example.com');
  
  expect(emailInput).toHaveValue('test@example.com');
});

Для проверки некорректного email можно использовать проверку валидации формы или свойства validity:

test('ввод некорректного email', async () => {
  render(<Form />);
  
  const emailInput = screen.getByPlaceholderText(/email/i);
  await userEvent.type(emailInput, 'invalid-email');
  
  expect(emailInput.validity.valid).toBe(false);
});

Использование validity позволяет интегрироваться с встроенной в браузер валидацией и тестировать её без реализации кастомной логики.


События и взаимодействия

Для всех типов input важно учитывать следующие события:

  • focus и blur — проверка поведения при переходе между полями
  • change — реакция на изменение значения
  • input — корректное обновление состояния компонента
  • submit — интеграция с кнопкой отправки формы

Пример с submit:

test('отправка формы с заполненными полями', async () => {
  const handleSubmit = jest.fn();
  render(<Form onSub mit={handleSubmit} />);
  
  const nameInput = screen.getByLabelText(/имя/i);
  const ageInput = screen.getByRole('spinbutton', { name: /возраст/i });
  const emailInput = screen.getByPlaceholderText(/email/i);
  const submitButton = screen.getByRole('button', { name: /отправить/i });
  
  await userEvent.type(nameInput, 'Иван Иванов');
  await userEvent.type(ageInput, '30');
  await userEvent.type(emailInput, 'ivan@example.com');
  
  await userEvent.click(submitButton);
  
  expect(handleSubmit).toHaveBeenCalledWith({
    name: 'Иван Иванов',
    age: 30,
    email: 'ivan@example.com'
  });
});

Тестирование формы через пользовательские события повышает надёжность и обеспечивает соответствие реальному поведению пользователя.


Полезные советы

  • Для числовых полей использовать getByRole('spinbutton') вместо getByLabelText, чтобы тесты учитывали семантику.
  • Проверять пустые значения через toHaveValue('') для текста и toHaveValue(null) для числа.
  • Для email-полей можно комбинировать проверки toHaveValue и validity.valid для максимальной надёжности.
  • Симуляция событий должна выполняться через userEvent, а не fireEvent, чтобы полностью имитировать поведение пользователя.

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