React Testing Library: When to use userEvent.click and when to use fireEvent
Asked Answered
R

4

75

I'm presently learning React-Testing-Library.

I'd like to test mouse interaction with an element. Presently it's a bit unclear to me the difference between userEvent.click(element) and fireEvent.click(element). Are both recommended for use, and in the example below are they being correctly implemented?

const mockFunction = jest.fn(() => console.info('button clicked'));
const { getByTestId } = render(<MyAwesomeButton onClick={mockFunction} />);
const myAwesomeButton = getByTestId('my-awesome-button');

// Solution A
fireEvent(myAwesomeButton)
expect(mockFunction.toHaveBeenCalledTimes(1);

// Solution B
userEvent.click(myAwesomeButton);
expect(mockFunction).toHaveBeenCalledTimes(1);

Thanks in advance for any clarity.

Reinhold answered 22/9, 2020 at 8:57 Comment(0)
A
95

Behind the scenes, userEvent uses the fireEvent. You can consider fireEvent being the low-level api, while userEvent sets a flow of actions.

Here is the code for userEvent.click

You can see that depending of which element you are trying to click, userEvent will do a set of different actions (e.g. if it's a label or a checkbox).

Arletha answered 22/9, 2020 at 11:4 Comment(1)
a good example is when you fire a click event on a button with fireEvent.click, the button will not be focused. The button is not focused. But with userEvent.click(), this button will be focused. useEvent can better reflect users' real behaviour.Fablan
E
27

According to Docs, you should use user-event to test interaction with your components.

fireEvent dispatches exactly the events you tell it to and just those - even if those exact events never had been dispatched in a real interaction in a browser.

User-event on the other hand dispatches the events like they would happen if a user interacted with the document. That might lead to the same events you previously dispatched per fireEvent directly, but it also might catch bugs that make it impossible for a user to trigger said events.

Enamor answered 8/2, 2022 at 9:57 Comment(0)
G
5

I want to build upon the accepted answer from @Guilhermevrs. There has been one change since, and that is that userEvent doesn't use fireEvent from v14 on, but this doesn't affect userEvent.click() directly.


To mimic user interactions like clicking buttons or inputting text for testing, the Testing Library provides various APIs. These APIs are:

  • fireEvent: This synchronous, low-level API works as a wrapper around the dispatchEvent Web API. For example, you can trigger a click event on a button using fireEvent.click(buttonElement) which would trigger an onClickhandler function on the component.
  • userEvent: An asynchronous, high-level API, userEvent simulates real user behaviour by triggering multiple events within a single interaction. The userEvent offers two primary methods to simulate keyboard and pointer interactions, along with utility methods expanding on these.
    • userEvent.keyboard(): This method replicates events like keyDown and keyUp using the dispatchEvent Web API.
    • userEvent.pointer(): Similar to userEvent.keyboard(), this method triggers a variety of pointer events using the dispatchEvent Web API. For example, userEvent.pointer({ keys: '[MouseLeft]', target: buttonElement}) triggers the pointerDown, mouseDown, pointerUp, mouseUp and click event.
    • Utility methods: These methods combine the previous two methods to closely simulate real user behavior.
      • userEvent.type(): This method emulates a click, keydown, keyup and input event. Hence, userEvent.type(inputElement, 'hello') triggers onClick, onKeyDown, onKeyPress, onKeyUp, and onChange handlers on the component.
      • userEvent.click(): This method emulates a hover and click event. For instance, userEvent.click(buttonElement) activates all the events from userEvent.pointer with mouseEnter, pointerEnter, mouseOver and pointerOver, but no mouseLeave and pointerLeave since the pointer will stay on the element.
      • ... you can find the others on the Utility and Convenience pages

Before version 14, userEvent internally used fireEvent. There was, however, a need for userEvent to have more control over the changes happening to the DOM. As a result, the internal calls to fireEvent have been removed in the later versions and userEvent.keyboard and userEvent.pointer themselves now expand on the dispatchEvent Web API

While userEvent is usually the preferred option because of its high-level abstraction and realism, you can fallback on fireEvent if you need to mimic edge case interactions that userEvent doesn't support.

userEvent limitations
To name a few limitations of userEvent:

  • it does not yet fully support keyboard composition sessions (#1097), which is the ability to simulate a user typing in text through the keyboard in a specific manner.
  • it inaccurately handles events in number input fields, where pressing ArrowUp/ArrowDown does not trigger an onChange event (#1066).
  • it falls short in simulating the interaction with datalist elements (#1088).

These limitations are not typically encountered in daily use cases. To circumvent these issues, consider using the keyboard or pointer modules. Alternatively, the lower-level API fireEvent can be used.

It should also be noted that using userEvent trades performance for accuracy. You remove risks of incorrect assumptions in your test - such as an why onKeyDown event not being triggered - when a seemingly simple thing like typing into a textbox is performed. But hence, userEvent can be 10x slower than a single fireEvent.

Grunion answered 21/11, 2023 at 8:53 Comment(0)
B
4

another good to mention difference between fireEvent and userEvent is that by default fireEvent is wrapped inside act function and this is useful when the user does some action and this action will cause component updates and re-render. On the contrary, if we used userEvent probably will notice "not wrapped in act(...)" warning error that appears in the console.

act(() => {
  userEvent.type(input, 'inputValue')
})

and here we don't need the act function, cause it's already wrapped over fireEvent

fireEvent.change(input, {target: {value: 'inputValue'}})

and this great article demonstrates this concept Common mistakes with React Testing Library

Boles answered 2/9, 2022 at 15:28 Comment(3)
Above answer said userEvent uses the fireEvent - wouldn't that imply it also already has act if fireEvent is wrapped?Moro
userEvent is already wrapped in act. You are probably seeing the acr(...) warning, because userEvent is async and you should await it.Eam
i agree with @Mister_CK, this advice is wrong, you forgot to await the useEvent.type().Gev

© 2022 - 2024 — McMap. All rights reserved.