skip to content

In Testing Library, what is the difference between fireEvent.click(button) and userEvent.click(button), and why is user-event the recommended default?

level: juniorimportance: must knowfreq 76%

answer

  1. one dispatch versus one interaction
  2. what the browser does before click
  3. pointer, mouse, focus, then click
  4. unreachable elements are refused, not clicked

basics

~20 s

fireEvent dispatches one synthetic DOM event. user-event replays the whole sequence a real interaction produces — pointer, mouse, focus and click events — and refuses to interact with elements a user could not touch, so it catches bugs fireEvent silently passes.

solid answer

~40 s

`fireEvent.click(el)` is a thin wrapper over `dispatchEvent`: exactly one `click` event lands on the element, and nothing else. A real click is a whole cascade — the browser fires pointer events, mouse events, moves focus, and only then fires `click`, and it does none of that if the element is disabled or has `pointer-events: none`. `userEvent.click(el)` reproduces that cascade and performs those checks, so the component is exercised the way a user exercises it. That matters because plenty of UI logic hangs off the parts fireEvent skips: `onMouseDown` handlers, focus and blur validation, dropdowns that close on outside pointer-down. I default to user-event for anything a human does with a mouse, keyboard or touch, and keep fireEvent for events a user cannot produce directly.

code

javascript · 18 lines
javascript
import { fireEvent } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'

const button = document.createElement('button')
button.textContent = 'Save'
document.body.append(button)

const seen = []
for (const type of ['pointerdown', 'mousedown', 'focus', 'pointerup', 'mouseup', 'click']) {
  button.addEventListener(type, (event) => seen.push(event.type))
}

fireEvent.click(button)
console.log(seen) // ['click']

seen.length = 0
await userEvent.setup().click(button)
console.log(seen) // ['pointerdown','mousedown','focus','pointerup','mouseup','click']

go deeper

for a junior

Be able to say plainly that fireEvent dispatches a single event while user-event performs a whole interaction, and that user-event is the default choice for clicking, typing and tabbing.

for a middle

Explain the actual event sequence a click produces and name concrete handlers that fireEvent would miss, such as mousedown-based dropdown dismissal or blur-based validation.

for a senior

Show judgment about test value: argue which of the two supports the claim you want to make about the feature, and where fireEvent remains correct for events no gesture produces.

for a principal

Own the convention. Decide how a codebase enforces interaction-level tests — lint rules against fireEvent for gestures, shared test utilities, and the migration cost of an existing fireEvent-heavy suite.

## Two very different levels of abstraction `fireEvent` and `userEvent` are often presented as interchangeable ways to "click a button in a test". They are not. They sit at different levels: `fireEvent` operates on **events**, `user-event` operates on **interactions**. `fireEvent.click(element)` constructs a `MouseEvent` of type `click` and calls `element.dispatchEvent(...)` on it. That is the whole story. No other event is created, no state changes, no checks are made about whether that element could receive a click at all. It is the testing equivalent of reaching into the page and poking one listener. ## What a browser actually does when a user clicks When a person presses a mouse button over a button, the browser emits a sequence roughly like: ```text pointerover → pointerenter → pointermove → pointerdown → mousedown → (focus moves to the element) → pointerup → mouseup → click ``` Before any of that, the browser decides *whether the element is interactive at all*: it hit-tests the coordinates, honours `pointer-events`, and suppresses interaction with disabled form controls. `user-event` models this. `await user.click(button)` walks the same ordered sequence of events, moves focus as the browser would, and tracks pointer state between calls. It also refuses to proceed when the target's effective `pointer-events` is `none`, throwing instead of quietly dispatching. ```javascript const user = userEvent.setup() await user.click(saveButton) // a whole interaction fireEvent.click(saveButton) // one event object ``` ## Why the difference changes what your tests catch A test is only as good as the bugs it can fail on. Everything `fireEvent.click` skips is a place where a real defect can hide and the test will still be green: - **Handlers on the skipped events.** Menus and popovers commonly close on `pointerdown`/`mousedown` rather than `click`; drag affordances start on `mousedown`. `fireEvent.click` never fires those, so the code path is untested — or worse, a test that asserts "the menu closed" passes for the wrong reason. - **Focus.** A real click focuses the clicked control and blurs the previous one. Field-level validation that runs on blur, or a combobox that commits its value on blur, is simply not exercised by a dispatched `click`. - **Unreachable elements.** A control styled `pointer-events: none`, or one sitting under a modal overlay, cannot be clicked by a human. `fireEvent.click` clicks it anyway and the test reports a feature that does not work as working. - **Event properties.** A real `click` carries `detail`, button state and modifier flags that user-event maintains; a bare `fireEvent.click` leaves them at defaults unless you pass them explicitly. The same argument applies to typing, tabbing and selecting: `userEvent.type` produces key events per character, `userEvent.tab()` moves focus along the tab order, `userEvent.selectOptions` goes through the real control interaction. ## The API shape follows from this Because one interaction is many events with possible delays between them, user-event's API (from v14 onwards) is asynchronous and instance-based: you create an instance with `userEvent.setup()` and `await` each interaction. `fireEvent`, being one dispatch, stays synchronous. The asymmetry is not an inconvenience to work around — it reflects that you asked for something bigger. ## When fireEvent is still the right tool User-event only models things a user can do. Events that nothing in the user's hands produces directly — `scroll` on a container, media events such as `play` or `ended`, `animationend`, `load`/`error` on an image, a custom event emitted by a third-party widget — have no user-event equivalent, and `fireEvent` is the correct and only tool. What you should not do is reach for `fireEvent.click` or `fireEvent.change` as a shortcut for a gesture user-event can perform. ## The rule of thumb Ask what you are claiming. "This component responds to a click event" is a weak claim about your own wiring. "A user can click this and the right thing happens" is the claim the product cares about — and only the interaction-level API can support it. Default to user-event; drop to fireEvent deliberately, for events with no gesture behind them.

  • If both APIs end up firing a click event, why would a test that uses fireEvent ever fail to catch a bug?
    Because the bug often lives in what fireEvent skips. A dropdown that closes on `mousedown`, a field that validates on blur, or a control styled `pointer-events: none` all behave correctly under a bare dispatched `click` and incorrectly for a real user. The test asserts your wiring, not the user's experience.
  • Does user-event replace your assertions about handler calls with something better?
    It changes what the assertion is worth rather than replacing it. Asserting `onClick` was called after a user-event click means a user could have caused that call; after a fireEvent click it only means the listener exists. Where possible, assert on the resulting DOM — the text that appeared, the row that vanished — rather than on the spy.
  • Is user-event slower, and does that matter?
    It does more work per interaction and can add a configured delay between keystrokes, so it is measurably slower than a single dispatch. In practice the difference is milliseconds per interaction against a test suite dominated by rendering, and the extra fidelity is exactly what you are paying for. Optimise elsewhere before trading it away.

fireEvent is like reaching into the machine and tripping one switch; user-event is like handing the machine a mouse and letting it decide which switches that trips.

saying these in an interview costs you the question

  • Says fireEvent and userEvent are the same thing with different syntax
  • Thinks fireEvent.click fires the full mouse and pointer sequence
  • Believes fireEvent moves focus to the clicked element
  • Claims user-event is only preferred because it is newer
  • Uses fireEvent.click as a shortcut for every gesture in a suite

context