Automated tests are important for most apps.
In this article, we’ll take a look at how to write tests for React components.
Mocking Modules
We can mock modules that don’t work well in a test environment.
For example, if we have the following components:
Map.js
import React from "react";
import { LoadScript, GoogleMap } from "react-google-maps";
export default function Map(props) {
return (
<LoadScript id="script-loader" googleMapsApiKey="YOUR_API_KEY">
<GoogleMap id="example-map" center={props.center} />
</LoadScript>
);
}
Contact.js
import Map from "./map";
export default function Contact({ name, email }) {
return (
<div>
<address>
{name} {email}
</address>
<Map center={props.center} />
</div>
);
}
Then we can test the Contact
component with a mocked Map
component by writing:
Contact.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Contact from "./contact";
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<div data-testid="map">
{props.center.lat}:{props.center.long}
</div>
);
};
});
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("should render contact information", () => {
const center = { lat: 0, long: 0 };
act(() => {
render(
<Contact
name="james"
email="test@example.com"
center={center}
/>,
container
);
});
expect(
container.querySelector("address").textContent
)
.toContain("james test@example.com");
expect(container.querySelector('[data-testid="map"]').textContent).toEqual(
"0:0"
);
});
We have:
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<div data-testid="map">
{props.center.lat}:{props.center.long}
</div>
);
};
});
to mock the Map
component.
Then when we call render
, we render with DummyMap
instead of the actual Map
component.
Events
To test events, we can dispatch real DOM events on DOM elements.
For instance, if we want to test the Toggle
component:
import React, { useState } from "react";
export default function Toggle(props) {
const [state, setState] = useState(false);
return (
<button
onClick={() => {
setState(previousState => !previousState);
props.onChange(!state);
}}
data-testid="toggle"
>
{state ? "off" : "on"}
</button>
);
}
Then we can add a test file for it by writing:
Toggle.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Toggle from "./toggle";
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("changes value when clicked", () => {
const onChange = jest.fn();
act(() => {
render(<Toggle onChange={onChange} />, container);
});
const button = document.querySelector("[data-testid=toggle]");
expect(button.innerHTML).toBe("on");
act(() => {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onChange).toHaveBeenCalledTimes(1);
expect(button.innerHTML).toBe("off");
act(() => {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onChange).toHaveBeenCalledTimes(2);
expect(button.innerHTML).toBe("on");
});
We mock the onChange
method that we pass in as the value of the onChange
prop.
Then we get the button with the selector[data-testid=toggle]
from the Toggle
component.
Then we can get the content of the button and how many times onChange
has been called after we call dispatchEvent
to dispatch a click MouseEvent
.
We need to pass in { bubbles: true }
so that React will delegate the event to the document.
Conclusion
We can mock modules that we can’t use conveniently in our tests.
Also, we can trigger events on elements and check the result after that.