userEvent против fireEvent: различия и выбор

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


fireEvent: низкоуровневый подход

fireEvent позволяет вручную инициировать события DOM, такие как click, change, input, submit. Он ближе к чистому DOM API и предоставляет прямой доступ к событиям.

Пример использования fireEvent:

import { render, fireEvent, screen } from '@testing-library/react';
import MyForm from './MyForm';

test('изменение значения input с помощью fireEvent', () => {
  render(<MyForm />);
  const input = screen.getByLabelText('Имя');
  
  fireEvent.change(input, { target: { value: 'Иван' } });
  
  expect(input.value).toBe('Иван');
});

Особенности fireEvent:

  • Позволяет вызывать отдельные события, не моделируя поведение пользователя полностью.
  • Не учитывает дополнительные события, которые обычно вызываются браузером при реальных взаимодействиях.
  • Быстрее в исполнении, поскольку работает на низком уровне без эмуляции реального поведения.
  • Подходит для простых тестов, когда важно проверить конкретное событие, а не весь поток взаимодействий.

userEvent: высокоуровневая симуляция

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

Пример использования userEvent:

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

test('изменение значения input с помощью userEvent', async () => {
  render(<MyForm />);
  const input = screen.getByLabelText('Имя');
  
  await userEvent.type(input, 'Иван');
  
  expect(input.value).toBe('Иван');
});

Преимущества userEvent:

  • Имитирует реальные действия пользователя: набор текста, клики, фокус, двойной клик, выделение текста.
  • Генерирует полную цепочку событий для каждого действия (keydownkeypressinputkeyup).
  • Подходит для тестирования сценариев взаимодействия, а не только конкретного события.
  • Улучшает реалистичность тестов и снижает риск пропуска багов, связанных с последовательностью событий.

Сравнение fireEvent и userEvent

Характеристика fireEvent userEvent
Уровень абстракции Низкий Высокий
Реалистичность Минимальная Близка к реальному пользователю
Обработка цепочек событий Нет Да
Скорость выполнения Высокая Немного медленнее
Основное применение Проверка отдельных событий Проверка пользовательских сценариев
Асинхронность Обычно синхронно Часто асинхронно (например, userEvent.type)

Когда использовать fireEvent

  • Тестирование конкретного события без учета последовательности действий.
  • Проверка внутренних обработчиков событий или простых вызовов функций.
  • Быстрое прототипирование тестов для отдельных компонентов.

Пример:

fireEvent.click(button);
expect(mockHandler).toHaveBeenCalledTimes(1);

Когда использовать userEvent

  • Тестирование пользовательских сценариев, где важна последовательность действий.
  • Симуляция ввода текста, выделения, кликов с модификаторами (Ctrl, Shift, Alt).
  • Проверка сложного поведения компонентов, зависящего от нескольких событий.

Пример:

await userEvent.click(dropdownButton);
await userEvent.click(optionItem);
expect(screen.getByText('Выбранная опция')).toBeInTheDocument();

Асинхронность и await

userEvent часто возвращает промис, особенно при вводе текста (type), что требует использования async/await. Это позволяет корректно обрабатывать задержки между событиями и обновления состояния компонентов.

await userEvent.type(input, 'Текст');

Использование await гарантирует, что все события завершены перед проверкой результата.


Подход к выбору

  • Если нужен низкоуровневый контроль, использование отдельных событий и высокая скорость — выбирается fireEvent.
  • Если важна реалистичная симуляция пользователя, последовательность действий и асинхронные эффекты — выбирается userEvent.
  • В современных тестах React Testing Library рекомендуется отдавать предпочтение userEvent, так как он ближе к реальным условиям работы приложения.

Заключение по использованию

fireEvent и userEvent дополняют друг друга: первый — для точечных проверок событий, второй — для комплексного тестирования поведения. Грамотное сочетание этих инструментов обеспечивает надёжность и читаемость тестов.

userEvent становится стандартом для симуляции пользовательских действий, особенно там, где важны естественные взаимодействия с интерфейсом, а fireEvent остаётся полезным для низкоуровневой отладки событий.