Тестирование пользовательских событий

Тестирование пользовательских событий — симуляция взаимодействий (клик, ввод текста, выбор, drag) в unit и integration тестах через @testing-library/user-event или fireEvent, и через прямые действия в E2E тестах.

Зачем нужно

Компоненты с интерактивностью (кнопки, формы, модальные окна, dropdown) нельзя полноценно проверить только рендером. Симуляция событий подтверждает что логика взаимодействия работает корректно и компонент реагирует на пользовательские действия.

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

  • React/Vue компоненты с кнопками, формами, вкладками, аккордеонами
  • Обработчики клавиатурных событий (Enter, Escape, Tab)
  • Drag and drop, scroll, resize обработчики
  • Тестирование accessibility: фокус, keyboard navigation

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

userEvent — основной инструмент RTL

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

test('клик на кнопку увеличивает счётчик', async () => {
  const user = userEvent.setup;
  render(<Counter initialValue={0} />);

  await user.click(screen.getByRole('button', { name: 'Увеличить' }));
  await user.click(screen.getByRole('button', { name: 'Увеличить' }));

  expect(screen.getByText('2')).toBeInTheDocument;
});

Ввод текста

test('вводит текст в поле поиска', async () => {
  const user = userEvent.setup;
  render(<SearchBar onSearch={jest.fn} />);

  const input = screen.getByRole('textbox', { name: 'Поиск' });
  await user.type(input, 'playwright');

  expect(input).toHaveValue('playwright');
});

test('очищает поле и вводит новый текст', async () => {
  const user = userEvent.setup;
  render(<SearchBar />);

  const input = screen.getByRole('textbox');
  await user.type(input, 'old query');
  await user.clear(input);
  await user.type(input, 'new query');

  expect(input).toHaveValue('new query');
});

Клавиатурные события

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

  await user.type(screen.getByRole('textbox'), 'query{Enter}');

  expect(onSubmit).toHaveBeenCalledWith('query');
});

test('закрывает модальное окно по Escape', async () => {
  const user = userEvent.setup;
  render(<Modal isOpen={true} />);

  await user.keyboard('{Escape}');

  expect(screen.queryByRole('dialog')).not.toBeInTheDocument;
});

Выбор в select

test('выбирает опцию в select', async () => {
  const onChange = jest.fn;
  const user = userEvent.setup;
  render(
    <select onChange={onChange}>
      <option value="ru">Русский</option>
      <option value="en">English</option>
    </select>
  );

  await user.selectOptions(screen.getByRole('combobox'), 'en');

  expect(onChange).toHaveBeenCalled();
  expect((screen.getByRole('combobox') as HTMLSelectElement).value).toBe('en');
});

Hover и focus

test('показывает tooltip при наведении', async () => {
  const user = userEvent.setup;
  render(<TooltipButton tooltip="Подсказка" label="Info" />);

  await user.hover(screen.getByRole('button'));

  await screen.findByText('Подсказка');
});

test('показывает outline при фокусе', async () => {
  const user = userEvent.setup;
  render(<Button>Нажми</Button>);

  await user.tab; // Tab устанавливает фокус

  expect(screen.getByRole('button')).toHaveFocus;
});

В Playwright/Cypress

// Playwright
await page.click('[data-testid="submit"]');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.keyboard.press('Enter');
await page.hover('[data-testid="info-icon"]');

// Cypress
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="email"]').type('user@example.com');
cy.get('[data-cy="select"]').select('option-value');

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

  • fireEvent вместо userEventfireEvent.click() не симулирует реальный flow (hover, focus, mousedown, mouseup, click); userEvent.click() делает всё правильно
  • Нет await перед userEvent.* — все методы асинхронны в v14+; без await события не применяются до проверки
  • Тест симулирует событие без проверки результата — после каждого события должен быть expect

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

Ресурсы