skip to content

User Event Simulation

userEvent drives realistic sequences such as press-in, press-out and per-key typing, while fireEvent calls one handler directly. Interviewers ask when fireEvent is still the right tool.

on this pageshow

explore

questions

5

In React Native Testing Library 14, how do you type a search and press a filter chip with userEvent, and why await each call?

level: juniorimportance: must knowfreq 50%

answer

  1. const user = userEvent.setup()
  2. user.type(input, text) on a host TextInput
  3. user.press(chip) on the pressable
  4. every call returns a Promise
  5. assert on what the screen shows

basics

~20 s

Create an instance with userEvent.setup(), then await user.type(searchInput, 'dune') and await user.press(chip), and assert on the rendered result. Each call returns a Promise that settles after its events and React updates, so skipping await lets assertions run too early.

solid answer

~50 s

With React Native Testing Library 14 I call `const user = userEvent.setup()` at the start of the test, after `await render(<MovieSearch />)`. I find the input by its label and call `await user.type(input, 'dune')`, which focuses the `TextInput`, emits key, change and `onChangeText` events one character at a time, then blurs it. Then `await user.press(screen.getByRole('button', { name: 'Sci-Fi' }))` presses the filter chip with the full press sequence. Every `userEvent` method is async: it waits between events and runs each dispatch inside `act`, so the promise resolves only after the handlers and React's re-render have finished. Without `await`, the assertion can run before the typed text or the filter is applied, and the updates may land outside `act`. Finally I assert on what the user would see, for example that the matching titles are on screen.

code

tsx · 13 lines
tsx
import { render, screen, userEvent } from '@testing-library/react-native';
import { MovieSearch } from './MovieSearch';

test('filters movies by query and genre', async () => {
  await render(<MovieSearch />);
  const user = userEvent.setup();

  await user.type(screen.getByLabelText('Search movies'), 'dune');
  await user.press(screen.getByRole('button', { name: 'Sci-Fi' }));

  expect(screen.getByText('Dune: Part Two')).toBeOnTheScreen();
  expect(screen.queryByText('Paddington')).not.toBeOnTheScreen();
});

go deeper

for a junior

Recall the pattern: await render, userEvent.setup, await user.type and user.press, then assert on the screen.

for a middle

Explain what type emits per character, that it appends to existing text, and why each call resolves only after its events and re-renders.

for a senior

Recognise missing awaits as the source of flaky or leaking tests, enforce them with lint rules, and separate interaction from waiting for async results.

for a principal

Standardise interaction helpers and review rules so every team writes tests in the same user-centred shape and flakiness stays measurable.

## The shape of an interaction test A React Native Testing Library interaction test has four steps: 1. **Render** the screen: `await render(<MovieSearch />)`. 2. **Set up user events**: `const user = userEvent.setup()`, once per test. 3. **Interact**, awaiting each call: `type`, `press`, `clear`, `paste`, `longPress`, `scrollTo`. 4. **Assert** on the output the user sees, not on internal state. `userEvent` also exposes direct calls such as `userEvent.press(element)` for compatibility with older code; they create a fresh default instance on every call. Calling `setup()` once keeps options such as `delay` in one place. ## What type and press do **`user.type(input, 'dune')`** works only on a host `TextInput` and throws for anything else. It: - presses into the input and fires `focus`; - for each character fires `keyPress`, `change`, `changeText` and `selectionChange`; - finishes with `endEditing` and `blur`. The component's `onChangeText` is therefore called four times, with `d`, `du`, `dun` and `dune`, as it would be on a device. Text is **appended** to the current value, so an input already holding text needs `user.clear(input)` first. **`user.press(chip)`** replays the press sequence on the pressable: press in, press out, press, taking at least 130 ms. ## Why every call must be awaited Since v14, `render`, `fireEvent` and `act` are async, and `userEvent` methods always were. Each method: - waits between events, by default `delay: 0`, which still yields to the event loop so other asynchronous code can run between keystrokes; - dispatches each event inside an async `act`, flushing state updates and effects; - resolves only when the whole sequence has finished. | Code | What happens | |---|---| | `await user.type(input, 'dune')` | All four `onChangeText` calls and their re-renders complete before the next line | | `user.type(input, 'dune')` without `await` | The next line runs before most or all of the events; assertions see stale UI | | `user.press(chip)` without `await` | The filter may apply after the test ends, producing `act` warnings or a failure in a later test | A missing `await` is the most common cause of flaky RNTL interaction tests. Lint rules for floating promises catch it mechanically. ## Choosing elements to interact with - Find the search field by its label: `screen.getByLabelText('Search movies')`. - Find the chip by role and name: `screen.getByRole('button', { name: 'Sci-Fi' })`. - Keep test IDs as a fallback for elements with no user-facing identity. ## Asserting the result After the interactions, assert on the rendered outcome: a matching title is visible, a non-matching one is not, the chip shows as selected. If the search is debounced or fetches data, the result appears later; waiting for it is the job of the async `findBy*` queries, not of more `userEvent` calls.

  • Why is onChangeText called four times when typing 'dune'?
    `user.type` simulates typing one character at a time, emitting `keyPress`, `change`, `changeText` and `selectionChange` for each key. The handler receives `d`, `du`, `dun` and `dune`, which exercises the same per-keystroke logic as a device, such as debouncing or validation.
  • What does userEvent.setup({ delay }) change?
    It sets the pause between subsequent events, such as keystrokes. The default is 0, which still yields to the event loop between events so other asynchronous code can run. A larger delay makes typing slower in real time and is rarely needed.

saying these in an interview costs you the question

  • userEvent methods are synchronous, so await is optional.
  • user.type replaces the existing text in the TextInput.
  • user.type works on any element that displays text.
  • You must wrap user.press in act() yourself.
  • userEvent.setup() must be called before render.
open as a page

In React Native Testing Library, how does userEvent.press differ from fireEvent.press, and when is fireEvent still the right tool?

level: middleimportance: must knowfreq 52%

basics

~20 s

fireEvent.press finds the nearest enabled onPress handler, walking up through composite components, and calls it once. userEvent.press replays React Native's press sequence on host elements, pressIn, pressOut, then press, in at least 130 ms. Keep fireEvent for events userEvent lacks.

open as a page

In React Native Testing Library, which events does userEvent.type emit on a TextInput, and how do clear, paste and the type options change that?

level: middleimportance: should knowfreq 30%

basics

~20 s

userEvent.type fires focus, then keyPress, change, changeText and selectionChange per character, then endEditing and blur, appending text and honouring maxLength. clear empties the field, paste replaces it in one change, and skipPress, skipBlur and submitEditing adjust the edges.

open as a page

A React Native Testing Library test logs 'not wrapped in act(...)' after pressing a movie filter chip; what causes the warning and how do you fix it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

A React state update ran outside act: usually an un-awaited user.press or fireEvent call, or async work it started, such as a fetch or debounce timer, settling after the test's last await. Await every event, then await the visible outcome.

open as a page

In React Native Testing Library, how do you simulate scrolling a FlatList of movies with userEvent.scrollTo so that more rows render?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Call await user.scrollTo(list, { y: 300, contentSize, layoutMeasurement }) on the FlatList's host ScrollView. Without contentSize and layoutMeasurement the list has no dimensions in the test, so it cannot compute a new window of rows to render.

open as a page