Testing React Components: The Mostly Definitive Guide (2019)

A living, breathing guide to testing React components. Learn how to test React with react-test-renderer, React Act API, Cypress, and more.

Testing React Components: The Mostly Definitive Guide

Testing React Components: who this guide is for and what you need to know

If you have a basic understanding of React and want to get your feet wet with testing components then this guide is for you. Before starting I suggest you take some time for investigating the theory around testing frameworks and Test-Driven development.

In this guide I assume you know what Jest is and what a testing framework does. If you need an introduction to Jest check out Jest Tutorial for Beginners: Getting Started With Jest for JavaScript Testing.

Testing React Components: what you will learn

In this guide you’ll learn:

  • unit testing React components
  • functional testing for React applications
  • the available tooling for testing React

And now enjoy the reading!

Testing React Components: getting to know snapshot testing

Snapshots are a common theme in technology: you can take a snapshot of a virtual private server for example. A snapshot is like a picture of an entity at a given point in time. And guess what is one of the simplest way for testing React components.

With snapshot testing you can take a picture of a React component and then compare the original against another snapshot later on. Snapshot testing is not specific to React, some people use snapshots for testing JavaScript code (an approach I’m not fan of). Snapshot testing is a feature built into the Jest test runner and since it’s the default library for testing React we’ll make use of it.

To start off create a new React project with create-react-app:

npx create-react-app testing-react-tutorial

Move inside the project folder and install react-test-renderer:

cd testing-react-tutorial && npm i react-test-renderer --save-dev

Next up create a new folder named __tests__, inside your project’s src folder (Jest will look there for new tests to run):

mkdir -p src/__tests__

Most of the times when creating a new React component I start off by creating a test for it. For those uninitiated this practice is called test-driven development and you don’t have to follow it literally.

It’s fine to start off coding without any test, especially when the idea of what implementation you’ll write is not yet formed in your mind. Anyway for the scope of this tutorial we’ll practice a bit of TDD with a test for a simple button component.

TIP: you can get method autocomplete for Jest by installing its type definitions with:

npm i @types/jest --save-dev

For our first snapshot test we need to import React, react-test-renderer and the component to test. Create a new file in src/__tests__/Button.spec.js with the following test:

import React from "react";
import { create } from "react-test-renderer";

describe("Button component", () => {
  test("Matches the snapshot", () => {
    const button = create(<Button />);
    expect(button.toJSON()).toMatchSnapshot();
  });
});

At this point you can run the first pass with:

npm test

and you’ll see the test fail because there is no button component. You can create a minimal implementation of the component in the same file:

import React from "react";
import { create } from "react-test-renderer";

function Button(props) {
  return <button>Nothing to do for now</button>;
}

describe("Button component", () => {
  test("Matches the snapshot", () => {
    const button = create(<Button />);
    expect(button.toJSON()).toMatchSnapshot();
  });
});

Now run again:

npm test

And watch the test pass. Great job! But let’s break down things a little bit.

Testing React Components: demystifying snapshot testing

At this point you might be asking what is react-test-renderer? react-test-renderer is a library for rendering React components to pure JavaScript objects while create is a method from react-test-renderer for “mounting” the component.

It’s worth noting that react-test-renderer does not use the real DOM. When you mount a component with react-test-renderer you’re interacting with a pure JavaScript object, a representation of the React component. Moving further into our test there is toMatchSnapshot, here’s the code again:

import React from "react";
import { create } from "react-test-renderer";

function Button(props) {
  return <button>Nothing to do for now</button>;
}

describe("Button component", () => {
  test("Matches the snapshot", () => {
    const button = create(<Button />);
    expect(button.toJSON()).toMatchSnapshot();
  });
});

toMatchSnapshot does all the heavy lifting under the hood. It:

  • creates a snapshot of the component if there isn’t any
  • checks if the component matches the saved snapshot

You can also see toJSON() called on the component’s instance.

In other words Jest (the test runner) takes a snapshot of the component on the first run, then it checks if the saved snapshot matches the actual component. At this point you may wonder how to choose between snapshot and other types of testing in React?

I have one rule of thumb: does your component changes often? If so, avoid snapshot testing. If you take a snapshot of a component the test passes on the first run but as soon as there is a change the test will fail because there’ll be a mismatch between the component and its original “picture”.

As you may guess snapshot tests are good for components that doesn’t change often. Put it another way: write a snapshot test when the component is stabilized.

And now let’s take a closer look at react-test-renderer!

Testing React Components: testing the wrong way (hands on react-test-renderer)

Suppose you have a button component in your application and the button should change its text from “SUBSCRIBE TO BASIC” to “PROCEED TO CHECKOUT” when clicked.

It appears the component has logic, could have a state too and that means a snapshot test would not be our best choice. react-test-renderer is a library for rendering React components to pure JavaScript objects but it can do a lot more than creating objects. In fact we can use react-test-renderer even for asserting the behaviour of our components.

Let’s create a fresh new test in src/tests/Button.spec.js:

import React from "react";
import { create } from "react-test-renderer";

describe("Button component", () => {
  test("it shows the expected text when clicked (testing the wrong way!)", () => {
    //
  });
});

As you can see I called the test “testing the wrong way”. Why so? Since we’re going to test a stateful component (a React component with its own state) we are naturally tempted to test the internal implementation of it. Let’s see. We’ll create an instance of the component with create and we’ll get that instance for asserting on its internal state.

NOTE: Since React hooks there would be no need to use ES2015 classes for using a component’s state. I wrote this article before hooks came out and I don’t want to ditch classes here. Later in the article you’ll find the hook equivalent of our button component.

I’m expecting the state text property to be empty for now:

import React from "react";
import { create } from "react-test-renderer";

describe("Button component", () => {
  test("it shows the expected text when clicked (testing the wrong way!)", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const instance = component.getInstance();
    expect(instance.state.text).toBe("");
  });
});

The test now is going to fail because I haven’t created the component yet. Let’s make a minimal implementation for the button component (in the same test file for convenience):

import React from "react";
import { create } from "react-test-renderer";

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text: "" };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(() => {
      return { text: "PROCEED TO CHECKOUT" };
    });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.text || this.props.text}
      </button>
    );
  }
}

describe("Button component", () => {
  test("it shows the expected text when clicked (testing the wrong way!)", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const instance = component.getInstance();
    expect(instance.state.text).toBe("");
  });
});

Run the test again and see how it passes. But up until this point I haven’t tested everything yet: how about handleClick? How can I test internal methods on my React components? Turns out we can call methods on our instance. Let’s update our test:

import React from "react";
import { create } from "react-test-renderer";

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text: "" };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(() => {
      return { text: "PROCEED TO CHECKOUT" };
    });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.text || this.props.text}
      </button>
    );
  }
}

describe("Button component", () => {
  test("it shows the expected text when clicked (testing the wrong way!)", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const instance = component.getInstance();
    expect(instance.state.text).toBe("");
    instance.handleClick();
    expect(instance.state.text).toBe("PROCEED TO CHECKOUT");
  });
});

Notice how I call:

instance.handleClick();

and then I assert that the state of the component changes as expected:

expect(instance.state.text).toBe("PROCEED TO CHECKOUT");

If I run the test again it still passes. But can you see the trap in this test? Are we testing the component from the user’s point of view? I have no clue whether my button will display the correct text to my users. I’m just asserting on it’s internal state. And that’s wrong. Let’s fix it.

Testing React Components: testing React components the right way

Testing the internal implementation of an object is always a bad idea. This holds true for React, JavaScript, and for any programming language out there. What we can do instead is testing the component by keeping in mind what the user should see. When building user interfaces the development process is driven (I really hope for you) by a functional test.

A functional test, or End to End test is a way of testing web applications from the user’s perspective.

(There is a lot of confusion and overlap among testing terminology. I suggest doing some research on your own for learning more about the various types of testing. For the scope of this guide functional testing === end to end testing).

For functional testing I love Cypress. But for now we can obtain the same result at the unit level with react-test-renderer. Let’s see how to refactor our test. We tested the internal implementation of the component, calling handleClick directly.

We bypassed completely the onClick props of our button:

      <button onClick={this.handleClick}>
        {this.state.text || this.props.text}
      </button>

And we didn’t test what the user should see. Can we do better? Turns out we can use .root instead of .getInstance() in our test. According to the documentation testRenderer.root “returns the root test instance object that is useful for making assertions about specific nodes in the tree”. Let’s find our button then! We can say:

const button = instance.findByType("button");

And from there I can access the button props:

button.props.onClick();
button.props.children;

Here’s the new test:

import React from "react";
import { create } from "react-test-renderer";

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text: "" };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(() => {
      return { text: "PROCEED TO CHECKOUT" };
    });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.text || this.props.text}
      </button>
    );
  }
}

describe("Button component", () => {
  test("it shows the expected text when clicked (testing the wrong way!)", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const instance = component.root;
    const button = instance.findByType("button");
    button.props.onClick();
    expect(button.props.children).toBe("PROCEED TO CHECKOUT");
  });
});

How does it look? Way better than the previous test, when we tested component’s state. Granted, we could also simulate the click event manually (we’ll see that later). But for now we’re good: we tested what the user should see and not the internal component’s state like we did before.

Always remember: do not test the implementation. Test the component from the user’s perspective. In other words: test what the user should see.

Well done. In the next section we’re going to see what testing means when using React hooks.

React hooks interlude: the Act API

Until React hooks there was only one way for keeping local state inside a React component: ES2015 classes.

JavaScript classes are great for programmers coming from languages like Java and C#, but they are verbose and less intuitive than a JavaScript function, especially for beginners.

They won’t die anytime soon (imagine how much React components were written with classes) but with hooks we can slim down our components a lot.

For example the Button component we created earlier becomes way more tiny with hooks:

import React, { useState } from "react";

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

Now how about testing a React component based on hooks? Can we just reuse the original test?

import React, { useState } from "react";
import { create } from "react-test-renderer";

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const instance = component.root;
    const button = instance.findByType("button");
    button.props.onClick();
    expect(button.props.children).toBe("PROCEED TO CHECKOUT");
  });
});

Run it with:

npm test

and look at the output:

    Warning: An update to Button inside a test was not wrapped in act(...).

    When testing, code that causes React state updates should be wrapped into act(...):

    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

Interesting. Our test does not work with React hooks. Turns out we need to use a new testing API for React called Act. “Luckily” there are two ways for writing tests with the Act API. Let’s see them.

React hooks interlude: Act API with react-test-renderer

If you can live with the fact that react-test-renderer does not use any DOM you’ll need just to tweak the test a bit for Act. That means importing act alongside with create:

import React, { useState } from "react";
import { create, act } from "react-test-renderer";

Your Button component will stay the same:

import React, { useState } from "react";
import { create, act } from "react-test-renderer";

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

But the test must use act() for any action that changes the component’s state, like “mounting” it or clicking on a function passed as a prop. Here’s the complete test with Act:

import React, { useState } from "react";
import { create, act } from "react-test-renderer";

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    let component;
    act(() => {
      component = create(<Button text="SUBSCRIBE TO BASIC" />);
    });
    const instance = component.root;
    const button = instance.findByType("button");
    act(() => button.props.onClick());
    expect(button.props.children).toBe("PROCEED TO CHECKOUT");
  });
});

Notice that both the call to create and to button.props.onClick() are wrapped in a callback passed to act(). That’s it if you don’t need the DOM. If instead you want to mount React components into the Document Object Model then another version of the Act API will suite you best. Head over the next section!

React hooks interlude: Act API with the real DOM

Since Act came out I’m using it almost exclusively. I pick react-test-renderer only for writing snapshot tests. The approach we’re going to see is my favourite because tests for me feel more real if I can interact with the DOM.

The Act API is available both on react-test-renderer and on react-dom/test-utils and when imported from the latter it’s possible to use ReactDOM.render, thus mounting the React component into the Document Object Model.

Still in src/__tests__/Button.spec.js wipe everything out and start by initializing the new test:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { act } from "react-dom/test-utils";

let container;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

// test here

We import act from react-dom/test-utils, ReactDOM, and more important we initialize a minimal DOM structure for our component. Next up we can create the actual test. Step 1, mount the component with Act:

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { act } from "react-dom/test-utils";

let container;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    act(() => {
      ReactDOM.render(<Button text="SUBSCRIBE TO BASIC" />, container);
    });
    // more soon
  });
});

Step 2, once mounted you can make assertions on the DOM which now contains your component:

    const button = container.getElementsByTagName("button")[0];
    expect(button.textContent).toBe("SUBSCRIBE TO BASIC");

You can also fire DOM events on the button. Here’s the complete test (it will pass without any problem):

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { act } from "react-dom/test-utils";

let container;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

function Button(props) {
  const [text, setText] = useState("");
  function handleClick() {
    setText("PROCEED TO CHECKOUT");
  }
  return <button onClick={handleClick}>{text || props.text}</button>;
}

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    act(() => {
      ReactDOM.render(<Button text="SUBSCRIBE TO BASIC" />, container);
    });
    const button = container.getElementsByTagName("button")[0];
    expect(button.textContent).toBe("SUBSCRIBE TO BASIC");
    act(() => {
      button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });
    expect(button.textContent).toBe("PROCEED TO CHECKOUT");
  });
});

NOTE: button.dispatchEvent seems to have effect even if the call is not wrapped inside act.

Seems a bit of boilerplate, especially calling all these DOM methods. It’s not a big deal for me but libraries like react-testing-library can help on this.

And now hands on mocking!

Testing React Components: mocking interlude

Fetching and displaying data is one of the most common use cases for a front-end library. This is usually done by contacting an external API which holds some JSON for us. In React you can use componentDidMount for making AJAX calls as soon as the component mounts. (And with hooks there is useEffect).

Now, the thing is: how do you test an AJAX call within React? Should you make a call to the actual API? Maybe! But some questions arise. Consider this: your team runs automated testing in a CI/CD environment, developers commit to the main branch 3/4 times a day.

What happens if the API goes down? The tests will fail with no reasons. And what if the API is paid? Clearly, contacting the real endpoint in testing is far from optimal. So? What’s the solution? We can use fakes and mocks.

Faking external requirements is a common pattern in testing. For example we can replace an external system with a fake during the testing phase. Since the advent of modern testing frameworks we can even mock functions.

Mocking is the act of replacing an actual function with a fake copy and in the next section we’ll mock an API call in Jest.

Mocking Fetch API calls with Jest

Again, let’s start with a test (act API on ReactDOM). Suppose we want a Users component for fetching and displaying a list of users. In our test we can mount the component and then assert on the output. Let’s give it a shot by preparing a test with the Act API, this time we’ll use unmountComponentAtNode from ReactDOM for cleaning up the test properly:

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

let container;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

describe("User component", () => {
  test("it shows a list of users", () => {
    act(() => {
      render(<Users />, container);
    });
  });
});

Run the test with npm test and see it fail. And now let’s make a minimal implementation of the React component. Here’s the class component (you can easily refactor the component to hooks):

import React, { Component } from "react";

export default class Users extends Component {
  constructor(props) {
    super(props);
    this.state = { data: [] };
  }

  componentDidMount() {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then(response => {
        // make sure to check for errors
        return response.json();
      })
      .then(json => {
        this.setState(() => {
          return { data: json };
        });
      });
  }
  render() {
    return (
      <ul>
        {this.state.data.map(user => (
          <li key={user.name}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

Import the component in your test file (I called it Users.spec.js) and let’s start by making a simple assertion on the container’s text content:

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Users from "../Users";

let container;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

describe("User component", () => {
  test("it shows a list of users", () => {
    act(() => {
      render(<Users />, container);
    });
    expect(container.textContent).toBe("What to expect?");
  });
});

Run the test with npm test and that’s what you get:

 FAIL  src/__tests__/Users.spec.js
  User component
    ✕ it shows a list of users (30ms)

  ● User component › it shows a list of users

    expect(received).toBe(expected) // Object.is equality

    Expected: "What to expect?"
    Received: ""

A failing test! There is no text content in the container. That makes sense because componentDidMount is calling Fetch which is asynchronous. With React 16.9 the Act API gained the ability to deal with asynchronous functions, and that means we can await on the component’s rendering like so:

describe("User component", () => {
  test("it shows a list of users", async () => {
    await act(async () => {
      render(<Users />, container);
    });
    expect(container.textContent).toBe("What to expect?");
  });
});

Be aware that Act cannot wait for componentDidMount and the real API endpoint is not getting hit. The test still fails with the same error, no text content inside the container.

That’s by design according to Facebook developers. Act won’t wait for the real API. In the end I think it’s a sane default because most of the times there is no reason to call external APIs during testing.

But its not that bad because we’re interested in mocking the API with a fake JSON response. That’s done with jest.spyOn. First we can provide a fake response and while we’re there let’s adjust the assertion (what follows is just the relevant part of the test suite):

describe("User component", () => {
  test("it shows a list of users", async () => {
    const fakeResponse = [{ name: "John Doe" }, { name: "Kevin Mitnick" }];

    await act(async () => {
      render(<Users />, container);
    });

    expect(container.textContent).toBe("John DoeKevin Mitnick");

  });
});

Next up we mock the actual Fetch API with jest.spyOn:

describe("User component", () => {
  test("it shows a list of users", async () => {
    const fakeResponse = [{ name: "John Doe" }, { name: "Kevin Mitnick" }];

    jest.spyOn(window, "fetch").mockImplementation(() => {
      const fetchResponse = {
        json: () => Promise.resolve(fakeResponse)
      };
      return Promise.resolve(fetchResponse);
    });

    await act(async () => {
      render(<Users />, container);
    });

    expect(container.textContent).toBe("John DoeKevin Mitnick");

    window.fetch.mockRestore();
  });
});

You may find this bit of code extremely hard to reason about, especially if you’re just starting out with testing:

    jest.spyOn(window, "fetch").mockImplementation(() => {
      const fetchResponse = {
        json: () => Promise.resolve(fakeResponse)
      };
      return Promise.resolve(fetchResponse);
    });

Worry not, here’s how it works. jest.spyOn “spies” on the Fetch method, available on the window object. When the method is called we mock, aka replace the real Fetch with a so called mock implementation (.mockImplementation). mockImplementation takes a function which is our fake Fetch.

TIP: check out Fetch API: Building a Fetch Polyfill From Scratch if you want to learn how Fetch works internally.

Inside the mock we create a new response object with a function called json. This function returns a resolved Promise with the fake JSON response. Finally we return the entire response object inside another resolved Promise. And that’s it! Now run the test again with npm test and see it passing! Fantastic. Main takeaways from this section?

Do not call a real API in your tests. Ideally tests should not depend on external systems. By mocking Fetch and providing a fake response we ensure test isolation. In the next section we’ll see how to stub the API response in a functional test. Remember, functional testing drives our development.

TIP: doing async calls in componentDidMount is poor practice for bigger projects. Consider always moving async logic out of React components. A good place for async logic is a Redux middleware.

Stubbing out responses with Cypress

In this guide we started straight away with unit tests but I should have done the other way around. In Double-loop TDD it’s functional testing that drives our development. We write a functional test for testing that our application satisfy some user stories. When the functional test fails we move to write unit and integration tests which in turn drive how we code our components.

It’s no secret that I love Cypress for functional testing. In this section I’ll show you how to stub out a response from an external API.

In the previous section we faked the response for Fetch. The way we do that is by mocking the real Fetch. That is, mocking means replacing a real function with a fake. Stubbing is a bit different. When I say stubbing out responses it means switching off the real API. How we do that in Cypress?

NOTE: At the time of this writing Cypress still does not support stubbing for Fetch unless using a polyfill. For making the following example work you can replace Fetch with Axios inside your Users component.

(Before moving on I suggest looking at my Cypress tutorial for learning about the syntax in Cypress).

Suppose I want to write a little functional test for my component. It could start like so:

describe("Some APP", () => {
  it("as a user I can see a list of people", () => {
    cy.visit("/");
    cy.contains("Valentino Gagliardi");
  });
});

This test will fail straight away because my React component is calling the real API:

Testing React Components: stubbing out responses with Cypress

Cypress is smart enough to give me an hint: notice XHR (XMLHttpRequest) in the picture. How can I avoid calling the real API?

Cypress gives you two powerful tools. All I have to do is calling cy.server and cy.route before visiting the page:

describe("Some APP", () => {
  it("as a user I can see a list of people", () => {
    cy.server();
    cy.route({
      method: "GET",
      url: "**/users",
      response: [{ name: "Valentino Gagliardi" }]
    });

    cy.visit("/");
    cy.contains("Valentino Gagliardi");
  });
});

Notice how I can stub out the response with cy.route. If I run the test again it passes:

Testing React Components: stubbing out responses with Cypress

Cypress gives you again a clear indication in the test’s detail: XHR Stub means that we’re not interacting with the real service. There’s even a “routes” section which gives you info about the routes we stubbed out. Neat!

Now, this looks really clever but as with mocking, stubbing out responses does not give any indication whether the app will work in production. Mocking and stubbing are great but I suggest using them with caution.

RESOURCES: if you want to learn more about Double-loop TDD look no further than Obey the testing goat by Harry Percival (it’s for Python but the concepts are the same fo every programming language). For learning about stubbing in Cypress check out the official doc: Network Requests. If you’re confused about mocking and stubbing check out What is the difference between a stub, a mock and a virtual service?

Conclusions

Some time ago I asked on Reddit: “What’s the consensus among the React community for testing React components?” Shawn Wang replied: “testing is an enormously complicated and nuanced topic on which there isn’t a consensus anywhere in JS, much less in React.” I was not trying to find the best library for testing React. Mainly because there isn’t one.

But there’s one truth, as we saw in the previous sections: use a library that encourages best practices and do not test the internal implementation, even if Enzyme or react-test-renderer make it easy. Here are my tools of choice for testing React apps:

  • react-test-renderer for snapshot unit testing
  • Act API for unit testing React components
  • Jest for unit and integration testing of JavaScript code
  • Cypress for end to end / ui testing

I also suggest taking a look at React testing library, a nice wrapper around the Act API.

I hope you learned something new from this article but at this point you might asking: why testing our React components? Why testing our applications? Everything is great until you play with React on your own but as soon as you hit the real world you’ll be in trouble.

I’m focusing on React here but really, it could be any other library or framework. As Jacob Kaplan-Moss said: “Code without tests is broken by design”. I bet it is! The problem is that most courses and tutorials don’t give a **** about testing. If you learned the basics of React don’t stop there. Begin asking yourself questions:

  • how to test React and Redux within a project?
  • what are acceptance tests? Do I know something about double-loop TDD?

and you’ll be on the road to become a great developer. In this post you learned:

  • how to unit test React components
  • functional testing, mocking, and stubbing
  • testing tools

Make sure to come back from time to time, the guide is constantly updated.

Thanks for reading and stay tuned!

23 Replies to “Testing React Components: The Mostly Definitive Guide (2019)”

  1. Hi!

    Thank you for sharing this, this is really helping me understand more regarding testing React components.

    I have a question regarding testing the api calls. What if, for example, in your componentDidMount() method, you need to call fetch 5 times for 5 different urls. How would I go in trying to test all these urls and making sure that the state is updated with the api results?

    Thanks!

    1. Hi Kenny and thanks for your comment. In your situation I would move AJAX call outside the component and test them in isolation. Or even better I would split the component in 5 little components, each one making its own call. But even then I would ask to myself: do I really need to call the API 5 times? Can I coalesce all the endpoints in a single one? But I want to stress on this point too: asynchronous logic should live outside React components if possible. Happy testing!

    2. The way you solve is like this :

      import axios from ‘axios’;
      import MockAdapter from ‘axios-mock-adapter’;
      import data from ‘../data.json’;

      const mock = new MockAdapter(axios);

      const url1 = new RegExp(‘http://domain.com/endpoint1/*’);
      mock.onGet(url1).reply(200, data);

      const url2 = new RegExp(‘http://domain.com/endpoint2/*’);
      mock.onGet(url2).reply(200, data);

  2. Hi,

    I came across your post trying to find a good way to test stateful components having async componentDidMount(). Thanks for sharing your thoughts on this.

    You provided a long list of good reasons for mocking external dependencies like axios. What are the reasons you consider async/await should stay away of React components, specifically out of componentDidMount?

    Thanks,
    /Costin

  3. Awesome work!
    This article expanded my vision about testing React and gives me some principles to follow with.

    Thank you Valentino!

  4. Thanks so much for sharing this. I do have a question though. Since the hooks API came out, the use of class components has dropped, so how would you go about testing stateful functional components?

  5. Thanks a lot for this article, I learned a lot.

    I am facing a problem, that you may already know how to deal with:

    I am using Material-UI, and some of the components I need to test are wrapped by a WithStyle component. Although calling the higher-level component componentDidMount function seems to end up calling the inner component function, I don’t seem to find a way to access the promise that the inner component method returns (probably because the WithStyle componentDidMount is not expecting it).

    So far, my best idea was to export both a wrapped version of the component for production and a non-wrapped one for component testing.

    I would like to hear if you have any thoughts on the matter. Thanks again for this amazing article.

  6. “Speaking of libraries I try to use Enzyme (and shallow rendering) as less as possible. And you should too. Kent C. Dodds has the same opinion on the matter.”

    Can you provide a better reason for not using Enzyme / shallow rendering rather than just an argument from authority? I didn’t see any other reference to Enzyme within the article, and it’s fallacious to assume that just because an expert has such a view, it should be generally accepted.

  7. Hi Valentino, I assume you are using React 16? Trying to implement the act API on React 15 but getting “Cannot find module ‘react-dom/test-utils’ from ‘.test.js'”. Very hard to find decent information on when the act API was implemented and as of which React version (but perhaps that’s due to me being new in the React/Node world).

  8. how to test React and Redux within a project? it’s a great question I would like to see how to dispatch actions and how the component should be tested when the state it passed to props changed. Maybe do you write an article like this?
    Thanks for this guide, I help me a lot.

  9. Hi Valentino,

    Thanks for the article. I just noticed that your example under “You can also fire events on the button. Here’s the complete test (it will pass without any problem)” also works without using act()

  10. I didn’t really learn how to test anything from this guide, it’s flitting from testing class
    based components to hooks, react-test-renderer to Act api, then Cypress. There’s way
    too much repetition of code. The best bit is the use of the name “Kevin Mitnick” in some
    test data, a hacker from the old days.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.