Author avatar

Jake Trent

Simulate Browser Events in React with ReactTestUtils

Jake Trent

  • Jul 17, 2019
  • 7 Min read
  • 242 Views
  • Jul 17, 2019
  • 7 Min read
  • 242 Views
Web Development
React

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 the correct parameters. 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. We will look at the most simple, built-in, and official way: using the ReactTestUtils from the react-dom library.

Use ReactTestUtils.Simulate

The utilities that will allow us to simulate browser events, such as click and change, are in the ReactTestUtils.Simulate module. This module contains all React-supported event types. We will try out two common ones:

  • ReactTestUtils.Simulate.click
  • ReactTestUtils.Simulate.change

Allow Components to Access DOM

Browser events are generated in the DOM, the elements that make up the markup of a web page. Because of this, the ReactTestUtils.Simulate API requires DOM elements to operate on.

But React components generally try to ignore the DOM and operate at the component level.There is a React mechanism, however, called refs, that allows a component to access the underlying DOM.

Let's say that we have a custom component called Button. If we pass it a special ref prop, the value of that prop will contain a pointer to its DOM element. To allow the ref to pass through, we must use the React.forwardRef API in our Button.js file:

1
2
3
4
5
import React from "react";

export default React.forwardRef(function Button(props, ref) {
  return <button {...props} ref={ref} />;
});
javascript

Setup DOM in Test

Now we'll set up our test code. We'll use the Jest APIs for testing that are standard in a create-react-app.Inside our test file, we'll set up to test the above Button component.

First, we'll get our imports out of the way. Then we need to create an element for our component to render within:

1
2
3
4
5
6
7
8
9
10
11
import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils";

import Button from "./Button.js";

let div;

beforeEach(() => {
  div = document.createElement("div");
});
javascript

And, since we're going to render into this div each test, we want to clean it up later as well:

1
2
3
afterEach(() => {
  ReactDOM.unmountComponentAtNode(div);
});
javascript

Simulate a Click Event

Now let's test an onClick prop on this custom Button component.The onClick should pass through to the component's underlying <button /> element because we're spreading ({...props}) that props through to it.

Let's create our test:

1
2
3
4
5
6
7
8
it("captures clicks", done => {
  const ref = React.createRef();
  function handleClick() {
    done();
  }
  ReactDOM.render(<Button onClick={handleClick} ref={ref} />, div);
  ReactTestUtils.Simulate.click(ref.current);
});
javascript

In the test, we create a ref so that we can pass it to the rendered Button and later to the Simulate.click call.

The onClick prop gets 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 on 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:

1
2
3
4
5
import React from "react";

export default React.forwardRef(function TextInput(props, ref) {
  return <input {...props} ref={ref} />;
});
javascript

Note that it's very simple, merely fronting an input element and passing a ref and all {...props} into it.

The test is very similar to the last time around as well:

1
2
3
4
5
6
7
8
9
it("captures changes", done => {
  const ref = React.createRef();
  function handleChange(evt) {
    expect(evt.target.value).toEqual("whamo");
    done();
  }
  ReactDOM.render(<TextInput onChange={handleChange} ref={ref} />, div);
  ReactTestUtils.Simulate.change(ref.current, { target: { value: "whamo" } });
});
javascript

Note that now we're calling the ReactTestUtils.Simulate.change method, and we're passing a second parameter. This parameter, an object, 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 stubbbing 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 the other events we might need to in a similar fashion.

The way we've just explored to simulate and test browser events is the simplest of the options. It required no other libraries - it just uses official libraries from the React team. And it wasn't bad. We were able to complete our task without much of a hassle. Ok, ensuring refs is a little bit of a hassle.

There are many other libraries and methods for being able to simulate browser events. One of my favorites worth checking out is the React Testing Library, which gives some convenient APIs for accessing the DOM and encourages black-box approaches in our tests. To try out this library, see the related guide Simulate Browser Events in React with React Testing Library.

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.

1