• Testing Recipes
    • Setup / Teardown
    • act()
    • Rendering
    • Data fetching
    • Mocking modules
    • Events
    • Timers
    • Snapshot testing
    • Multiple renderers
    • Something missing?

    Testing Recipes

    Common testing patterns for React components.

    Note:

    This page assumes you’re using Jest as a test runner. If you use a different test runner, you may need to adjust the API, but the overall shape of the solution will likely be the same. Read more details on setting up a testing environment on the Testing Environments page.

    On this page, we will primarily use function components. However, these testing strategies don’t depend on implementation details, and work just as well for class components too.

    Setup / Teardown

    For each test, we usually want to render our React tree to a DOM element that’s attached to document. This is important so that it can receive DOM events. When the test ends, we want to “clean up” and unmount the tree from the document.

    A common way to do it is to use a pair of beforeEach and afterEach blocks so that they’ll always run and isolate the effects of a test to itself:

    1. import { unmountComponentAtNode } from "react-dom";
    2. let container = null;
    3. beforeEach(() => {
    4. // setup a DOM element as a render target
    5. container = document.createElement("div");
    6. document.body.appendChild(container);
    7. });
    8. afterEach(() => {
    9. // cleanup on exiting
    10. unmountComponentAtNode(container);
    11. container.remove();
    12. container = null;
    13. });

    You may use a different pattern, but keep in mind that we want to execute the cleanup even if a test fails. Otherwise, tests can become “leaky”, and one test can change the behavior of another test. That makes them difficult to debug.

    act()

    When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. React provides a helper called act() that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions:

    1. act(() => {
    2. // render components
    3. });
    4. // make assertions

    This helps make your tests run closer to what real users would experience when using your application. The rest of these examples use act() to make these guarantees.

    You might find using act() directly a bit too verbose. To avoid some of the boilerplate, you could use a library like React Testing Library, whose helpers are wrapped with act().

    Note:

    The name act comes from the Arrange-Act-Assert pattern.

    Rendering

    Commonly, you might want to test whether a component renders correctly for given props. Consider a simple component that renders a message based on a prop:

    1. // hello.js
    2. import React from "react";
    3. export default function Hello(props) {
    4. if (props.name) {
    5. return <h1>Hello, {props.name}!</h1>;
    6. } else {
    7. return <span>Hey, stranger</span>;
    8. }
    9. }

    We can write a test for this component:

    1. // hello.test.js
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. import Hello from "./hello";
    6. let container = null;
    7. beforeEach(() => {
    8. // setup a DOM element as a render target
    9. container = document.createElement("div");
    10. document.body.appendChild(container);
    11. });
    12. afterEach(() => {
    13. // cleanup on exiting
    14. unmountComponentAtNode(container);
    15. container.remove();
    16. container = null;
    17. });
    18. it("renders with or without a name", () => {
    19. act(() => {
    20. render(<Hello />, container);
    21. });
    22. expect(container.textContent).toBe("Hey, stranger");
    23. act(() => {
    24. render(<Hello name="Jenny" />, container);
    25. });
    26. expect(container.textContent).toBe("Hello, Jenny!");
    27. act(() => {
    28. render(<Hello name="Margaret" />, container);
    29. });
    30. expect(container.textContent).toBe("Hello, Margaret!");
    31. });

    Data fetching

    Instead of calling real APIs in all your tests, you can mock requests with dummy data. Mocking data fetching with “fake” data prevents flaky tests due to an unavailable backend, and makes them run faster. Note: you may still want to run a subset of tests using an “end-to-end” framework that tells whether the whole app is working together.

    1. // user.js
    2. import React, { useState, useEffect } from "react";
    3. export default function User(props) {
    4. const [user, setUser] = useState(null);
    5. async function fetchUserData(id) {
    6. const response = await fetch("/" + id);
    7. setUser(await response.json());
    8. }
    9. useEffect(() => {
    10. fetchUserData(props.id);
    11. }, [props.id]);
    12. if (!user) {
    13. return "loading...";
    14. }
    15. return (
    16. <details>
    17. <summary>{user.name}</summary>
    18. <strong>{user.age}</strong> years old
    19. <br />
    20. lives in {user.address}
    21. </details>
    22. );
    23. }

    We can write tests for it:

    1. // user.test.js
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. import User from "./user";
    6. let container = null;
    7. beforeEach(() => {
    8. // setup a DOM element as a render target
    9. container = document.createElement("div");
    10. document.body.appendChild(container);
    11. });
    12. afterEach(() => {
    13. // cleanup on exiting
    14. unmountComponentAtNode(container);
    15. container.remove();
    16. container = null;
    17. });
    18. it("renders user data", async () => {
    19. const fakeUser = {
    20. name: "Joni Baez",
    21. age: "32",
    22. address: "123, Charming Avenue"
    23. };
    24. jest.spyOn(global, "fetch").mockImplementation(() =>
    25. Promise.resolve({
    26. json: () => Promise.resolve(fakeUser)
    27. })
    28. );
    29. // Use the asynchronous version of act to apply resolved promises
    30. await act(async () => {
    31. render(<User id="123" />, container);
    32. });
    33. expect(container.querySelector("summary").textContent).toBe(fakeUser.name);
    34. expect(container.querySelector("strong").textContent).toBe(fakeUser.age);
    35. expect(container.textContent).toContain(fakeUser.address);
    36. // remove the mock to ensure tests are completely isolated
    37. global.fetch.mockRestore();
    38. });

    Mocking modules

    Some modules might not work well inside a testing environment, or may not be as essential to the test itself. Mocking out these modules with dummy replacements can make it easier to write tests for your own code.

    Consider a Contact component that embeds a third-party GoogleMap component:

    1. // map.js
    2. import React from "react";
    3. import { LoadScript, GoogleMap } from "react-google-maps";
    4. export default function Map(props) {
    5. return (
    6. <LoadScript id="script-loader" googleMapsApiKey="YOUR_API_KEY">
    7. <GoogleMap id="example-map" center={props.center} />
    8. </LoadScript>
    9. );
    10. }
    11. // contact.js
    12. import React from "react";
    13. import Map from "./map";
    14. function Contact(props) {
    15. return (
    16. <div>
    17. <address>
    18. Contact {props.name} via{" "}
    19. <a data-test-id="email" href={"mailto:" + props.email}>
    20. email
    21. </a>
    22. or on their <a data-test-id="site" href={props.site}>
    23. website
    24. </a>.
    25. </address>
    26. <Map center={props.center} />
    27. </div>
    28. );
    29. }

    If we don’t want to load this component in our tests, we can mock out the dependency itself to a dummy component, and run our tests:

    1. // contact.test.js
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. import Contact from "./contact";
    6. import MockedMap from "./map";
    7. jest.mock("./map", () => {
    8. return function DummyMap(props) {
    9. return (
    10. <div data-test-id="map">
    11. {props.center.lat}:{props.center.long}
    12. </div>
    13. );
    14. };
    15. });
    16. let container = null;
    17. beforeEach(() => {
    18. // setup a DOM element as a render target
    19. container = document.createElement("div");
    20. document.body.appendChild(container);
    21. });
    22. afterEach(() => {
    23. // cleanup on exiting
    24. unmountComponentAtNode(container);
    25. container.remove();
    26. container = null;
    27. });
    28. it("should render contact information", () => {
    29. const center = { lat: 0, long: 0 };
    30. act(() => {
    31. render(
    32. <Contact
    33. name="Joni Baez"
    34. email="test@example.com"
    35. site="http://test.com"
    36. center={center}
    37. />,
    38. container
    39. );
    40. });
    41. expect(
    42. container.querySelector("[data-test-id='email']").getAttribute("href")
    43. ).toEqual("mailto:test@example.com");
    44. expect(
    45. container.querySelector('[data-test-id="site"]').getAttribute("href")
    46. ).toEqual("http://test.com");
    47. expect(container.querySelector('[data-test-id="map"]').textContent).toEqual(
    48. "0:0"
    49. );
    50. });

    Events

    We recommend dispatching real DOM events on DOM elements, and then asserting on the result. Consider a Toggle component:

    1. // toggle.js
    2. import React, { useState } from "react";
    3. export default function Toggle(props) {
    4. const [state, setState] = useState(false);
    5. return (
    6. <button
    7. onClick={() => {
    8. setState(previousState => !previousState);
    9. props.onChange(!state);
    10. }}
    11. data-testid="toggle"
    12. >
    13. {state === true ? "Turn off" : "Turn on"}
    14. </button>
    15. );
    16. }

    We could write tests for it:

    1. // toggle.test.js
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. import Toggle from "./toggle";
    6. let container = null;
    7. beforeEach(() => {
    8. // setup a DOM element as a render target
    9. container = document.createElement("div");
    10. // container *must* be attached to document so events work correctly.
    11. document.body.appendChild(container);
    12. });
    13. afterEach(() => {
    14. // cleanup on exiting
    15. unmountComponentAtNode(container);
    16. container.remove();
    17. container = null;
    18. });
    19. it("changes value when clicked", () => {
    20. const onChange = jest.fn();
    21. act(() => {
    22. render(<Toggle onChange={onChange} />, container);
    23. });
    24. // get a hold of the button element, and trigger some clicks on it
    25. const button = document.querySelector("[data-testid=toggle]");
    26. expect(button.innerHTML).toBe("Turn off");
    27. act(() => {
    28. button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    29. });
    30. expect(onChange).toHaveBeenCalledTimes(1);
    31. expect(button.innerHTML).toBe("Turn on");
    32. act(() => {
    33. for (let i = 0; i < 5; i++) {
    34. button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    35. }
    36. });
    37. expect(onChange).toHaveBeenCalledTimes(6);
    38. expect(button.innerHTML).toBe("Turn on!");
    39. });

    Diffrent DOM events and their properties are described in MDN. Note that you need to pass { bubbles: true } in each event you create for it to reach the React listener because React automatically delegates events to the document.

    Note:

    React Testing Library offers a more concise helper for firing events.

    Timers

    Your code might use timer-based functions like setTimeout to schedule more work in the future. In this example, a multiple choice panel waits for a selection and advances, timing out if a selection isn’t made in 5 seconds:

    1. //card.js
    2. import React, { useEffect } from "react";
    3. export default function Card(props) {
    4. useEffect(() => {
    5. const timeoutID = setTimeout(() => {
    6. props.onSelect(null);
    7. }, 500);
    8. return () => {
    9. clearTimeout(timeoutID);
    10. };
    11. }, [props.onSelect]);
    12. return [1, 2, 3, 4].map(choice => (
    13. <button
    14. key={choice}
    15. data-test-id={choice}
    16. onClick={() => props.onSelect(choice)}
    17. >
    18. {choice}
    19. </button>
    20. ));
    21. }

    We can write tests for this component by leveraging Jest’s timer mocks, and testing the different states it can be in.

    1. // card.test.js
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. jest.useFakeTimers();
    6. let container = null;
    7. beforeEach(() => {
    8. // setup a DOM element as a render target
    9. container = document.createElement("div");
    10. document.body.appendChild(container);
    11. });
    12. afterEach(() => {
    13. // cleanup on exiting
    14. unmountComponentAtNode(container);
    15. container.remove();
    16. container = null;
    17. });
    18. it("should select null after timing out", () => {
    19. const onSelect = jest.fn();
    20. act(() => {
    21. render(<Card onSelect={onSelect} />, container);
    22. });
    23. // move ahead in time by 100ms
    24. act(() => {
    25. jest.advanceTimersByTime(100);
    26. });
    27. expect(onSelect).not.toHaveBeenCalled();
    28. // and then move ahead by 1 second
    29. act(() => {
    30. jest.advanceTimersByTime(1000);
    31. });
    32. expect(onSelect).toHaveBeenCalledWith(null);
    33. });
    34. it("should cleanup on being removed", () => {
    35. const onSelect = jest.fn();
    36. act(() => {
    37. render(<Card onSelect={onSelect} />, container);
    38. });
    39. act(() => {
    40. jest.advanceTimersByTime(100);
    41. });
    42. expect(onSelect).not.toHaveBeenCalled();
    43. // unmount the app
    44. act(() => {
    45. render(null, container);
    46. });
    47. act(() => {
    48. jest.advanceTimersByTime(1000);
    49. });
    50. expect(onSelect).not.toHaveBeenCalled();
    51. });
    52. it("should accept selections", () => {
    53. const onSelect = jest.fn();
    54. act(() => {
    55. render(<Card onSelect={onSelect} />, container);
    56. });
    57. act(() => {
    58. container
    59. .querySelector("[data-test-id=2]")
    60. .dispatchEvent(new MouseEvent("click", { bubbles: true }));
    61. });
    62. expect(onSelect).toHaveBeenCalledWith(2);
    63. });

    You can use fake timers only in some tests. Above, we enabled them by calling jest.useFakeTimers(). The main advantage they provide is that your test doesn’t actually have to wait five seconds to execute, and you also didn’t need to make the component code more convoluted just for testing.

    Snapshot testing

    Frameworks like Jest also let you save “snapshots” of data with toMatchSnapshot / toMatchInlineSnapshot. With these, we can “save” the renderered component output and ensure that a change to it has to be explicitly committed as a change to the snapshot.

    In this example, we render a component and format the rendered HTML with the pretty package, before saving it as an inline snapshot:

    1. // hello.test.js, again
    2. import React from "react";
    3. import { render, unmountComponentAtNode } from "react-dom";
    4. import { act } from "react-dom/test-utils";
    5. import pretty from "pretty";
    6. import Hello from "./hello";
    7. let container = null;
    8. beforeEach(() => {
    9. // setup a DOM element as a render target
    10. container = document.createElement("div");
    11. document.body.appendChild(container);
    12. });
    13. afterEach(() => {
    14. // cleanup on exiting
    15. unmountComponentAtNode(container);
    16. container.remove();
    17. container = null;
    18. });
    19. it("should render a greeting", () => {
    20. act(() => {
    21. render(<Hello />, container);
    22. });
    23. expect(
    24. pretty(container.innerHTML)
    25. ).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */
    26. act(() => {
    27. render(<Hello name="Jenny" />, container);
    28. });
    29. expect(
    30. pretty(container.innerHTML)
    31. ).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */
    32. act(() => {
    33. render(<Hello name="Margaret" />, container);
    34. });
    35. expect(
    36. pretty(container.innerHTML)
    37. ).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */
    38. });

    It’s typically better to make more specific assertions than to use snapshots. These kinds of tests include implementation details so they break easily, and teams can get desensitized to snapshot breakages. Selectively mocking some child components can help reduce the size of snapshots and keep them readable for the code review.

    Multiple renderers

    In rare cases, you may be running a test on a component that uses multiple renderers. For example, you may be running snapshot tests on a component with react-test-renderer, that internally uses ReactDOM.render inside a child component to render some content. In this scenario, you can wrap updates with act()s corresponding to their renderers.

    1. import { act as domAct } from "react-dom/test-utils";
    2. import { act as testAct, create } from "react-test-renderer";
    3. // ...
    4. let root;
    5. domAct(() => {
    6. testAct(() => {
    7. root = create(<App />);
    8. });
    9. });
    10. expect(root).toMatchSnapshot();

    Something missing?

    If some common scenario is not covered, please let us know on the issue tracker for the documentation website.