Simulate Browser Events in React with React Testing Library
Jul 17, 2019 • 6 Minute Read
Why Simulate
When unit testing React components, it's often useful to be able to simulate browser events. This enables us to assert that event handlers are called and assert correct arguments. We simulate browser events because, often, we don't want to require a real browser in our test environment, especially in our unit tests.
There are many different ways to simulate browser events. Here, we will look at a solid third-party library that gives us a great API and an approach for testing browser events, React Testing Library.
Use @testing-library/react fireEvent
The utilities that will allow us to simulate browser events, such as click and change, are in the React Testing Library's fireEvent module. This module contains many different supported events. We will try out two common ones:
- fireEvent.click
- fireEvent.change
To use the library, first install it:
npm install @testing-library/react --save-dev
A Simple Component
To have something to test, let's set up a simple component:
import React from "react";
export default function Button(props) {
return <button {...props} />;
}
There's not much of anything going on here. We're creating the simplest possible custom component and spreading props onto a button element.
Simulate a Click Event
Let's test an onClick prop on this custom Button component. We'll use the jest APIs that are standard in create-react-app:
import {
cleanup,
fireEvent,
render
} from "@testing-library/react";
import Button from "./Button.js";
it("captures clicks", done => {
function handleClick() {
done();
}
const { getByText } = render(
<Button onClick={handleClick}>Click Me</Button>
);
const node = getByText("Click Me");
fireEvent.click(node);
});
afterEach(cleanup);
First, we import the needed functions from @testing-library/react.
In our test, we call render from that library, passing in the usage of our Button component. React Testing Library handles setting up the DOM for test then rendering into that DOM. To make sure that DOM is getting cleaned up between tests, so each can run independently, we call afterEach(cleanup).
The render function call returns many things. In this example, we're deconstructing one of React Testing Library's getBy functions, specifically getByText. This is a function that will search in the rendered DOM of this specific test for the element that has matching innerText. We look for "Click Me", in the example.
Once we have this node, we're able to dispatch the click event on it with fireEvent.click.
The onClick prop to <Button /> is the value of a handleClick event handler function. That function calls done(), which is the callback to the it function`.
Note that passing this callback makes the test async, meaning it will not complete until the callback is called. If it is never called, the test will time out, failing. Conversely, if it is called, it will indicate that the click was simulated on the button and the handler is called. And with the above code, it is!
Simulate a Change Event
Let's try another kind of browser event simulation. This time, we'll simulate a change event. This kind of event is useful for something like a text input field, where we want to know when the input value changes and what the current value actually is.
We'll create a TextInput.js component:
import React from "react";
export default function TextInput(props) {
return <input {...props} />;
}
Note that it's, again, very simple, merely fronting an input element passing all the {...props} into it.
The test is very similar to the last time around, as well:
it("captures changes", done => {
function handleChange(evt) {
expect(evt.target.value).toEqual("whamo");
done();
}
const { getByPlaceholderText } = render(
<TextInput onChange={handleChange} placeholder="Change Me" />
);
const node = getByPlaceholderText("Change Me");
fireEvent.change(node, { target: { value: "whamo" } });
});
This time, to find the node to fire events on, we're using a different query function, getByPlaceholderText. And, we're calling the fireEvent.change function, passing a new, second parameter. This object we pass as an argument represents the Event object. For instance, it includes the target property, usually indicating the element that the Event was dispatched from. Here, we're simply stubbing a value.
Then, in our handleChange handler, we can assert the value of the event target to come through as "whamo" (always a great input value).
One of Many Methods
We've now simulated two different browser events. We should be able to handle any other events we might need to in a similar fashion.
This is a solid community library that gives us some great APIs for event simulation and other things. The query methods that we've used have also helped us not reach into our component implementation but rather treat it as a black box, inspecting the DOM itself (the things that a normal client of our component would be able to see).
There are many other libraries and methods for being able to simulate browser events. One of the most basic, not requiring any additional testing library like this is using the official react-dom/test-utils. To explore this approach, see the related guide Simulate Browser Events in React with ReactTestUtils.
Experiment and try a few approaches, then choose one appropriate to your tastes and project.
To run the tests in the above examples on your own machine, check out this demo-react-simulate-test project.