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.