{ Testing with Enzyme. }

Adding Enzyme

Now that we can run simple specs and take snapshots of our components, let's see how we can test exactly what a component is rendering, as well as certain props that it will have.

To do this, we're going to install an additional library called Enzyme which is made by the wonderful people at AirBnB. Enzyme uses React testing utilities, but it is a nice abstraction and makes testing components quite easy.

In the previous example with snapshot testing, we imported the renderer function from react-test-renderer. These functions, which are part of the React testing utilities, are not always the easiest to use. This is especially true when you need to find certain elements on the page. To make this easier, we're going to use Enzyme, which uses a very similar DOM selection API to jQuery (it uses a library called Cheerio, which you might have come across when learning about web scraping with Node).

To get started we simply need to run npm install --save-dev enzyme to install it!

We're going to be using Enzyme to test the content of our React Components. When using Enzyme, there are three different functions we can use:

shallow

Shallow rendering: this is useful to test a component in isolation from every other component. In the typical React pattern of smart and dumb components, shallow rendering is usually used to test ‘dumb’ components (stateless components) in terms of their props and the events that can be simulated. You should be using shallow most of the time.

You can read more about it here.

To see this in action, let's continue with our earlier example by including our SimpleComponent inside of our App component:

import React, { Component } from "react";
import logo from "./logo.svg";
import SimpleComponent from "./SimpleComponent";
import "./App.css";

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <SimpleComponent first="Matt" last="Lane" favFood="ice cream" />
      </div>
    );
  }
}

export default App;

Next, let's modify the App.test.js file as follows:

import React from "react";
import ReactDOM from "react-dom";
import App from "../App";
import SimpleComponent from "../SimpleComponent";
import { shallow } from "enzyme";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<App />, div);
});

test("should render one <SimpleComponent /> component", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(SimpleComponent)).toHaveLength(1);
  expect(wrapper.find("h2")).toHaveLength(1);
});

test("should render with a class of App-intro", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(".App-intro")).toHaveLength(1);
});

As you can see, this test is able to detect that the App component consists of a single SimpleComponent child, and that there's an element with a class of App-intro. Note also that with shallow rendering, only one h2 is found: this is the one inside of the App component, not the one inside of SimpleComponent.

mount

Mounting: Also known as full DOM rendering, it allows you to render a part of the DOM tree and it also gives you access to the lifecycle methods of React components (componentWillMount, componentWillReceiveProps , etc…)

You can read more about it here. For now, to highlight some simple differences between shallow and mount, let's add the following lifecycle hook to our App.js:

componentDidMount() {
  this.setState({ name: "My app!" });
}

Next, let's modify our test file by importing mount and adding a couple more test blocks:

import React from "react";
import ReactDOM from "react-dom";
import App from "../App";
import SimpleComponent from "../SimpleComponent";
import { shallow, mount } from "enzyme";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<App />, div);
});

test("should render one <SimpleComponent /> component", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(SimpleComponent)).toHaveLength(1);
  expect(wrapper.find("h2")).toHaveLength(1);
});

test("should render with a class of App-intro", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(".App-intro")).toHaveLength(1);
});

test("should detect no state on shallow rendering", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.state()).toBeNull();
});

test("should detect state and all h2s on mounting", () => {
  const wrapper = mount(<App />);
  expect(wrapper.state("name")).toBe("My app!");
  expect(wrapper.find("h2")).toHaveLength(2);
});

Note: mount has the potential to greatly slow down your test runs since it is a full DOM mount. When calling mount over shallow, always ask yourself "do I need to do a full mount for this test?" and if so "why". Testing is often a great opportunity to rethink your component structure in terms of stateless vs. stateful, etc.

render

Static rendering: Is sparsely used but when it is the case, serves as means of testing plain HTML. This may be useful if you care about testing the eventual HTML that gets rendered, and not the React Component structure.

You can read more about it here. For now, let's import render and write one more test block:

import React from "react";
import ReactDOM from "react-dom";
import App from "../App";
import SimpleComponent from "../SimpleComponent";
import { shallow, mount, render } from "enzyme";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<App />, div);
});

test("should render one <SimpleComponent /> component", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(SimpleComponent)).toHaveLength(1);
  expect(wrapper.find("h2")).toHaveLength(1);
});

test("should render with a class of App-intro", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.find(".App-intro")).toHaveLength(1);
});

test("should detect no state on shallow rendering", () => {
  const wrapper = shallow(<App />);
  expect(wrapper.state()).toBeNull();
});

test("should detect state and all h2s on mounting", () => {
  const wrapper = mount(<App />);
  expect(wrapper.state("name")).toBe("My app!");
  expect(wrapper.find("h2")).toHaveLength(2);
});

test("<SimpleComponent /> should have props show up", () => {
  const wrapper = render(<App />);
  expect(wrapper.text()).toContain("Matt");
  expect(wrapper.text()).toContain("Lane");
  expect(wrapper.text()).toContain("ice cream");
});

If you are wondering when to use each of these methods, you can read a great post here.

Coverage

As you start writing more tests, it's good to know how much of your application is covered by tests. "Code coverage" refers to the number of lines that have been executed after the tests run.

Let's see what our application looks like so far with coverage!

npm test -- --coverage

When we run this, we will see a nice looking table to show how much coverage our code has, and it is broken down by types of coverage (explanation of different types of coverage here). This is done on a per file basis and gives us a somewhat-useful metric for how thoroughly our code has been tested. It's not realistic that you will always have 100% coverage or that 100% coverage will prevent all bugs, so there are diminishing returns if you try to overly maximize your coverage. It's essential to test your code, but you don't have to go overboard with coverage.

For more information, the test coverage that is built in to Jest is provided by Istanbul.

Additional Resources

Intro to Enzyme

Enzyme API Reference

Jest API Reference

MDN Code Coverage Article

Exercise

Complete the Todo testing Exericse

When you're ready, move on to React Router Introduction

Continue

Creative Commons License