SkillByAIOpen interactive version →

Lesson 17 / 25

User Interactions with user-event

Simulate real typing and clicking.

user-event over fireEvent

fireEvent dispatches one DOM event. Real interactions are sequences: a click involves pointer down/up, focus changes and the click itself; typing fires keydown, keypress, input and keyup per character and respects maxLength and disabled fields. @testing-library/user-event simulates those sequences. In version 14, create a session with const user = userEvent.setup() before rendering, and await every call: await user.click(el), await user.type(input, 'hello'), await user.clear(input), await user.selectOptions(select, 'UK'), await user.keyboard('{Enter}'), await user.tab(). If you use fake timers, pass advanceTimers to setup so user-event's internal delays advance too.

Filling in and submitting a form

The submit handler is a mock prop.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, it, vi } from 'vitest';
import { LoginForm } from './LoginForm';

it('submits the email and password', async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByRole('textbox', { name: 'Email' }), 'asha@example.com');
  await user.type(screen.getByLabelText('Password'), 'correct horse');
  await user.click(screen.getByRole('button', { name: 'Log in' }));

  expect(onSubmit).toHaveBeenCalledWith({ email: 'asha@example.com', password: 'correct horse' });
});

it('keeps the button disabled until both fields are filled', async () => {
  const user = userEvent.setup();
  render(<LoginForm onSubmit={vi.fn()} />);

  await user.type(screen.getByRole('textbox', { name: 'Email' }), 'asha@example.com');
  expect(screen.getByRole('button', { name: 'Log in' })).toBeDisabled();
});

A real hand versus a poke

fireEvent is poking a button with a stick from across the room; user-event is a hand that moves the mouse, presses, releases and lets the page react to every step, exactly as a person would.

Quick check: How should user-event v14 interactions be called?

  • Wrap every call in setTimeout
  • Call userEvent.click synchronously and never await
  • Create user = userEvent.setup() and await each call such as await user.click(button)
  • Only through fireEvent
Answer

Create user = userEvent.setup() and await each call such as await user.click(button) — The v14 API is asynchronous.