Testing React Components: The Mostly Definitive Guide (ft. react-test-renderer)

A living, breathing guide to testing React components. Constantly updated, better you bookmark it!

Testing React Components: The Mostly Definitive Guide (ft. react-test-renderer)

Web development is fantastic! You know HTML, CSS, JavaScript, and React! You’re ready to build fantastic web applications people will use and love! It sounds like a dream … until you land your first job as a front-end developer.

I don’t want to scare you but imagine this.

Molly, the lead developer, introduces you to a project after a brief tour of the offices. She goes explaining how the app works and at some point she says:

“Ok, Gunnar here’s your task. We are in the process of rebuilding the application. I want you to rewrite it from scratch. But I want you to follow a TDD approach. You should write a good testing suite for this project”.

You panic. TDD? Testing suite? Does this story sound familiar to you?

Testing React Components: the need for Test-Driven development

At this point you might asking: why do we need to test our React components? Why do we need to test our applications at a glance?

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! I would also say: in a web application without tests your users will be the beta testers.

Are you ok with that? Because I’m not. Would you trust un-tested code from other developers?

I know you spent months learning the basics of React and now you feel like you did nothing. 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 these questions:

How to test React and Redux within a project?

What are acceptance tests? Do I know something about double-loop TDD?

It could sound like propaganda but I guarantee it’s not. There’s no way to survive in the market without these skills. So here’s a living, breathing guide to all things testing in React.

Enjoy the reading!

Testing React Components: who this guide is for

If you have a basic understanding of React then this guide is for you.

I suggest also taking 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.

Testing React Components: what you will learn

In this guide you learn how to:

  • unit test React components
  • functional testing within React applications

and much more!

Testing React Components: getting to know snapshot testing

One of the simplest way for testing React components is snapshot testing.

Snapshots are a common theme in technology: you can take a snapshot of a virtual private server. Or you can take a snapshot of a volume in AWS.

And so on.

Snapshot testing in React is not built into the library itself: instead it’s a feature built into Jest.

Snapshot testing leans more toward UI testing but some people use snapshots for testing JavaScript code (an approach I’m not fan of).

So, how does snapshot testing work?

Jest (the testing runner) takes a snapshot of the component on the first run, then it checks if the saved snapshot matches the actual component.

How does a snapshot test looks like?

For snapshot testing a React component you can create a new file in a folder called __tests__, inside your project’s src folder.

Jest will look there for new tests to run.

Then in the test file you should import React, react-test-renderer, and the component you want to test.

And here’s an example of snapshot testing:

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

describe("Feature component", () => {
  test("it matches the snapshot", () => {
    const component = create(<FeatureComponent />);
    expect(component.toJSON()).toMatchSnapshot();
  });
});

But let’s break down things a little bit. What is react-test-renderer?

react-test-renderer is a library for rendering React components to pure JavaScript objects. Guess what, it is a good way for testing our components in the most simple way.

And what is create? create is a method from react-test-renderer for “mounting” the component.

Well, the component does not get mounted like you could expect. Instead, create creates an instance of the component, which you can make assertions on.

It is worth nothing that react-test-renderer does not use the real DOM.

It makes sense, the component does not mount, but a pure JavaScript representation of it gets created instead.

Moving further into our test there is toMatchSnapshot which is a method provided by Jest.

toMatchSnapshot works this way:

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

View this post on Instagram

Snapshot testing is the simplest way for testing React components. A snapshot is exactly what its name suggests. Jest (the testing runner) takes a snapshot of the component on the first run. Then it checks if the saved snapshot matches the actual component. How does a snapshot test looks like? For snapshot testing a React component you can create a new file in a folder called __tests__, inside your src folder. Jest will look there for new tests to run. Then in the test file you should import React, react-test-renderer, and the component you want to test. . . . . . #tdd #testing #javascript #es6 #frontend #webdev #webdevelopment #codinglife #codelife #code #reacttraining #react #reactjs #javascripttraining #tech #developer #devcoach #html #css #tutorial #coder #geek #geeklife #programmerslife #codingisfun

A post shared by Valentino Gagliardi (@valentinogagliardi) on

At this point one of the most frequent question is: how to choose between snapshot testing 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. But what happens when you modify the component?

The test will fail. And you’ll have to delete the snapshot. It doesn’t sound like a big deal but …

As you may guess snapshot tests are good for component that doesn’t change often.

Put it another way: consider writing a snapshot test when you’re sure the component is stabilized.

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

Testing React Components: hands on react-test-renderer

react-test-renderer is a library for rendering React components to pure JavaScript objects.

But it seems that react-test-renderer can do a lot more than creating objects. In fact we can use react-test-renderer for asserting the behaviour of our components.

Let’s say we want to create a button component.

The button should change its text from “SUBSCRIBE TO BASIC” to “PROCEED TO CHECKOUT” when clicked.

Since we’re doing Test-Driven development the discipline imposes you to write a failing test before doing anything else.

It appears the component has some minimal logic into it. It could have a state too.

That means a snapshot test would not be our best choice. We want to test methods and logic for our component. How would you go about that?

react-test-renderer can help us testing these kind of components.

Let’s see how into the next section!

Testing React Components: testing the wrong way (do not test the implementation)

View this post on Instagram

đź“Ś TDD the wrong way. When you're testing a stateful component (a React component with its own state) you're naturally tempted to test the internal implementation of it. Don't do that. You may want to take another path: testing the output of the component. Do not test the implementation: test the component from the user's perspective. In other words: test what the user should see. . . . . . #tdd #testing #javascript #es6 #frontend #webdev #webdevelopment #codinglife #codelife #code #reacttraining #react #reactjs #javascripttraining #tech #developer #devcoach #html #unittesting #webapp #webapplicationtesting #engineer #engineering #softwaredeveloper #softwareengineer #softwaretesting #programmerslife

A post shared by Valentino Gagliardi (@valentinogagliardi) on

We need a button component for our application.

The button should change its text from “SUBSCRIBE TO BASIC” to “PROCEED TO CHECKOUT” when clicked.

Let’s create a new test in __tests__/Button.spec.js:

// Button.spec.js

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

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

As you can see I labeled the test as “testing the wrong way”.

Why so?

Since we’re testing 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.

Next we’ll get that instance for asserting on its internal state.

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

// Button.spec.js

import React from "react";
import { create } from "react-test-renderer";
import Button from "../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("");
  });
});

If I run the test with npm test it will fail because I haven’t created the component yet.

Let’s make it! Here is a minimal implementation for the button component:

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

  handleClick() {
    this.setState(this.stateUpdater);
  }

  stateUpdater() {
    return { text: "PROCEED TO CHECKOUT" };
  }

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

Now if I run the test again it passes.

Up until this point I haven’t tested everything yet: how about handleClick? Even if it’s bad practice, how can I test internal methods on my React components?

With react-test-renderer we can call methods on our instance.

Let’s update our test:

// Button.spec.js

import React from "react";
import { create } from "react-test-renderer";
import Button from "../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 can say:

instance.handleClick();

and then I can 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?

You can try on your own: remove the button from the component’s render method. Change the return statement and return null. Run the test again and… you can see it still passes!

Are we testing the component from the user’s point of view? No. I have no clue whether my button will display the correct text to my users.

Is there a way to test that?

Testing React Components: testing components the right way (testing from the user perspective)

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 from the user’s point of view.

For example in the double-loop TDD the development is driven 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 tend to use Cypress a lot.

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 left here:

// Button.spec.js

import React from "react";
import { create } from "react-test-renderer";
import Button from "../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");
  });
});

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 testRenderer.root instead of .getInstance():

// Button.spec.js

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

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);   
    const rootInstance = component.root;

  });
});

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 = rootInstance.findByType("button");

And from there I can finally access the button props:

button.props.onClick();

Last but not least I can snitch into the button props:

button.props.children

And here’s the complete test:

// Button.spec.js

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

describe("Button component", () => {
  test("it shows the expected text when clicked", () => {
    const component = create(<Button text="SUBSCRIBE TO BASIC" />);
    const rootInstance = component.root;
    const button = rootInstance.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 the component’s state.

Granted, we could also simulate the click event with Enzyme or react-testing-library (we’ll se that in one of the next sections).

But for now we’re good: we finally tested what the user should see and not the internal component’s state like we did before.

Remember: when testing a stateful component (a React component with its own state) you may be tempted to test its internal state.

Don’t do that.

You may want to take another path: testing the output of the component.

Do not test the implementation: test the component from the user’s perspective. In other words: test what the user should see.

Testing React Components: test doubles and mocks

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 will use the componentDidMount lifecycle method for making an AJAX call as soon the component mounts.

You can also use async/await for componentDidMount (even though there are some caveats).

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. The team commits to the main branch 3/4 times a day.

What happens if the API goes down? The tests will fail with no reason at all.

And what happens if every call to the API costs money?

Clearly, contacting the real API during 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, although some developers say that mocking is a code smell.

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. For learning more about mocks and stubs check out TestDouble by Gerard Meszaros.

For now we’ll focus on mocking GET requests in Axios. Axios is one of the most popular library for making AJAX requests and with Jest you can mock its functionalities.

Let’s see how to mock .get, which is an axios method for making GET requests.

TIP: fetch is a valid, native alternative to axios, even though it lacks some feature. These days I still prefer axios over fetch because of its testability. In fact at the time of this writing Cypress does not support stubbing for fetch.

Testing React Components: mocking Axios with Jest

Again, let’s start with a test.

Suppose we want a Users component for fetching and displaying a list of users.

A naive implementation could use async/await in componentDidMount (although it’s a bad idea).

That means when laying down our test we can await on componentDidMount.

Here’s our test’s skeleton:

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

describe("Users component", () => {
  it("shows a list of users", async () => {
    const component = create(<Users />);
    const instance = component.getInstance();
    await instance.componentDidMount();
    //
  });
});

You should be already familiar with create and getInstance from react-test-renderer.

Notice also how you can use async/await in Jest.

You probably saw something similar in a previous post of mine, when I covered testing Koa with Jest and Supertest.

Also worth noting in the above example: I’m calling componentDidMount on the testing instance.

Now if I run npm test the above test fails because I haven’t created the component yet.

Better we make a minimal implementation for it!

It could be a stateful component which gets data from an remote API with Axios, and then displays such data to our users:

import React, { Component } from "react";
import axios from "axios";

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

  handleGet(response) {
    this.setState(this.stateUpdater(response));
  }

  stateUpdater(response) {
    return { data: response.data };
  }

  async componentDidMount() {
    const response = await axios.get(
      "https://jsonplaceholder.typicode.com/users"
    );
    this.handleGet(response);
  }

  render() {
    return (
      <ul>
        {this.state.data.map(user => (
          <li key={user.name}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

Nothing new uh?

I couldn’t resist using async/await on componentDidMount but let’s be clear: it is bad practice. You should always move asynchronous logic out of React components.

Anyway for this example it’s ok, we won’t do any harm.

I must confess I can’t resist to console.log too.

Are you ok if I snitch inside the state of my testing instance?

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

describe("Users component", () => {
  it("shows a list of users", async () => {
    const component = create(<Users />);
    const instance = component.getInstance();
    await instance.componentDidMount();
    console.log(instance.state) // << HERE IS THE SNITCH!
  });
});

Surprise! After running instance.componentDidMount() I can see the new state being updated!

Test-Driven React: The Definitive Guide to Testing React Components. Calling the real API with Jest

There’s also what appears to be some JSON coming from jsonplaceholder.typicode.com.

We called the real API!

Is that ok? In short, no. You should never, ever call a real API while testing.

If the API slow down your automated tests will suffer and break.

If the API goes down your tests will break too.

If the API is paid you’ll be charged every time you run npm test.

Ok, I’m sold Valentino! I really want to mock axios! Now what?

Jest makes mocking Axios easy as a breeze.

For mocking axios.get we should:

import axios inside our test

use jest.mock for mocking the axios module

provide a custom response for axios.get

use mockResolvedValue for faking the asynchronous response

Let’s do it:

import React from "react";
import { create } from "react-test-renderer";
import Users from "../Users";
import axios from "axios";

jest.mock("axios");

describe("Users component", () => {
  it("shows a list of users", async () => {
    const response = {
      data: [{ name: "Kevin Mitnick" }, { name: "Valentino Gagliardi" }]
    };
    axios.get.mockResolvedValue(response);
    const component = create(<Users />);
    const instance = component.getInstance();
    await instance.componentDidMount();
    console.log(instance.state); // << HERE IS THE SNITCH!
  });
});

If you take a look again at instance.state after running the test you’ll see the fake data inside the state:

Test-Driven React: The Definitive Guide to Testing React Components. Mocking axios with Jest

We succeded in mocking axios!

Now we can complete our test with some assertions (and remove console.log).

We want to test that our users effectively see 2 li elements from this React component.

If you remember, when testing React components we shouldn’t care about the internal state.

We’re interested in testing what the user should see.

For making the test pass I want to search for all the li elements, asserting on the expected text content:

import React from "react";
import { create } from "react-test-renderer";
import Users from "../Users";
import axios from "axios";

jest.mock("axios");

describe("Users component", () => {
  it("shows a list of users", async () => {
    const response = {
      data: [{ name: "Kevin Mitnick" }, { name: "Valentino Gagliardi" }]
    };
    axios.get.mockResolvedValue(response);
    const component = create(<Users />);
    const instance = component.getInstance();
    await instance.componentDidMount();
    const root = component.root;
    const listOfLi = root.findAll(element => element.type === "li");
    expect(listOfLi[0].props.children).toBe("Kevin Mitnick");
    expect(listOfLi[1].props.children).toBe("Valentino Gagliardi");
  });
});

Notice the use of component.root and findAll called on the root instance.

Now the test passes! Fantastic.

What’s the takeaway from this section?

When possibile you should not call a real API in automated testing. Ideally tests should not depend on external systems. By mocking axios and providing a fake response we ensure test isolation.

But faking a response from an API has some downsides too. If I were to change the url in componentDidMount like that:

async componentDidMount() {
  const response = await axios.get(
    "https://blablabala.typicode.com/users"
  );
  this.handleGet(response);
}

what do you think it happens? The test will pass as long as I mock axios with a fake response. But I won’t see any user if I open the application in a browser. The code works in testing but fails in “production”. Mocking and faking are really useful but there are some tradeoffs.

In the next section we’ll see how to stub the API response in a functional test. Remember, functional testing drives our development.

TIP: don’t forget to return the promise from axios if you prefer using axios.get().then() in componentDidMount. Otherwise you wouldn’t be able to test it:

componentDidMount(){
   return axios.get(someurl).then(doStuff).catch(handleErr)
}

TIP: doing async calls in componentDidMount is considered bad practice. Consider always moving async logic out of React components. A good place for this kind of thing is a Redux middleware.

Testing React Components: 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 from axios.get. The way we do that is by mocking axios. 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?

(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?

Testing React Components: a quick detour. What’s the consensus among the React community for testing components?

I asked this question on Reddit some time ago: “What’s the consensus among the React community for testing React components?”

I was in the process of preparing a training program and I was wondering if there was a library of choice for testing components.

Shawn Wang commented on Reddit: “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: always test the expected behaviour of your components. Do not test the internal implementation, even if Enzyme or react-test-renderer make it easy.

Speaking of libraries I try to use Enzyme (and shallow rendering) as less as possible. And you should avoid shallow rendering too. Kent C. Dodds gives the same advice in Why I Never Use Shallow Rendering.

(Kent has created a nice testing library for React: react-testing-library. I suggest you take a look!).

In the end I decided to focus this guide mainly on react-test-renderer because it’s the low hanging fruit for testing React components. Plus is 100% supported by the React team.

This is a living, breathing guide to testing in React. Constantly updated, better you bookmark it!

Valentino Gagliardi

Valentino Gagliardi

I help busy people to learn Web Development and JavaScript - DevOps Consultant
Valentino Gagliardi

3 Replies to “Testing React Components: The Mostly Definitive Guide (ft. react-test-renderer)”

  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!

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.