Codementor Events

Unit Testing React Components: Jest or Enzyme?

Published Sep 30, 2016Last updated Jan 18, 2017
Unit Testing React Components: Jest or Enzyme?

Background

Testing React components can be a difficult task to do especially when you're just starting with it. Even the basic task of knowing what tools to test it with can already be confusing. Two popular options for testing React components are Jest and Enzyme. Developers working with React are confronted with the question: What should I use, Jest or Enzyme?

Here are just a few threads that have made mention of this comparison, or has offered one as an alternative for the other:

This tutorial aims to set the record straight: instead of asking "Jest or Enzyme", a more appropriate way to look at it should be "Jest and Enzyme!"

Editor's note: for this same purpose, this tutorial has been rewritten from its original version published on 20th September 2016. This current version has been edited for clarity.


Introduction

React is a UI library for writing components, and unit testing React components is much more organized.

Before we talk about Enzyme and Jest, we should define a few terms: Test runner, assertion library, and mocking library.

  • Test runner — a tool that picks up files that contain unit tests, executes them, and writes the test results to the console or log files. Mocha and Jasmine are two popular test runners used within the JavaScript community. You can read a comparison between the two here.
  • Assertion library — verifies the results of a test. Chai, Should, and Expect are examples of JavaScript assertion libraries.
  • Mocks — used in unit testing a component. A component under test has many dependencies. These dependencies are usually replaced by stubs or mocks. Stubs simulate a dependent object. Mocks offer an additional feature over stubs. With mocks, tests can be written to verify if the component under test has called the mocks as expected.
  • Mocking library — facilitates the usage of mocks in unit testing. Sinon and TestDouble are commonly used JavaScript mocking libraries.

Enzyme

From the Enzyme documentation:

Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.

Enzyme is a library that wraps packages like React TestUtils, JSDOM and CheerIO to create a simpler interface for writing unit tests. React TestUtils has methods to render a react component into a document and simulate an event. JSDOM is a JavaScript implementation of the DOM (Document object model). DOM represents the tree structure of UI components. CheerIO implements a subset of jQuery core and is used to query the DOM. Enzyme wraps these libraries and provides a simple and intuitive API for unit testing.

Enzyme is not a unit testing framework. It does not have a test runner or an assertion library. It works with any test runner and assertion library. In that way, it is not opinionated.

Jest

Quoting the Jest documentation:

Jest is a JavaScript unit testing framework, used by Facebook to test services and React applications.

Jest is a framework and not a library. It comes with a test runner, assertion library, and good mocking support. Jest is built on top of Jasmine.

Jest has a novel way to test react components: Snapshot testing. With snapshot testing, the output of the current test run is compared with the snapshot of the previous test run. If the output matches the snapshot, the test passes.

Comparing Enzyme with Jest

As mentioned earlier, most developers who want to write unit tests for a React application compare the two alternatives: Enzyme or Jest. But in reality, this comparison is like comparing apples and oranges. Enzyme is a testing library to render the react component into the DOM and query the DOM tree. Jest is a unit testing framework and has a test runner, assertion library, and mocking support. Enzyme and Jest is complementary. Enzyme can be used within Jest.

However, there are valid reasons why developers compare the two.

  • Both Enzyme and Jest are specifically designed to test React applications.
  • Enzyme works only with React.
  • Jest is more of a testing framework and can be used with non-react applications.
  • Not many people use Jest outside of React applications. So, there is a tendency to compare the two.

That said, instead of comparing the two, this tutorial will show to how to use Jest and Enzyme together to test React applications.

Getting started with Jest and Enzyme

The first step is to install React.

npm install --save react react-dom

We will create a simple React component which will display a Hello world message.

export default class Welcome extends React.Component {
  render() {
    return (
      <div>Hello world</div>
    );
  }
}

The Welcome component is a React component that uses JSX. To parse JSX and ES6 code, some add-on packages are required.

npm install --save-dev 
babel-core 
babel-loader 
babel-polyfill 
babel-preset-es2015 
babel-preset-react

In addition, a babelrc file is required in our project which uses the ES2015 and React preset. The contents of the .babelrc file is shown below.

{
  "presets": ["es2015", "react"]
}

Next, we install Jest.

npm install --save-dev
jest
babel-jest
react-test-renderer

Jest looks for tests in a folder called __tests__ . We will explore snapshot testing using Jest.

import React from 'react';
import renderer from 'react-test-renderer';
import Welcome from '../src/client/components/welcome.jsx';

describe('Welcome (Snapshot)', () => {
  it('Welcome renders hello world', () => {
    const component = renderer.create(<Welcome />);
    const json = component.toJSON();
    expect(json).toMatchSnapshot();
  });
});

Jest requires the react-test-renderer package to render the component to JSON. The describe function is used to write a test suite. The it function is used to define a test. The expect function is part of the assertion library exposed by Jest. The toMatchSnapshot method compares the JSON output with the JSON snapshot in the previous test run.

To run the tests, type jest in the command line. The console output on running jest is shown below.
unit testing react components

On running the test, a snapshot file is created within the __snapshots__ folder. The snapshot for our component is shown below.

exports[`Welcome (Snapshot) Welcome renders hello world 1`] = `
<div>
  Hello world
</div>
`;

Snapshot testing is another new idea from Facebook. It provides an alternate way to write tests without any assertions. To write tests using assertions, Enzyme is quite useful.

Installing enzyme is straight-forward.

npm install --save-dev enzyme

The test for the Welcome component using Enzyme is shown below.

import React from 'react';
import expect from 'expect';
import { shallow } from 'enzyme';
import Welcome from '../src/client/components/welcome.jsx';

describe('Welcome', () => {
  it('Welcome renders hello world', () => {
    const welcome = shallow(<Welcome />);
    expect(welcome.find('div').text()).toEqual('Hello world');
  });
});

The shallow function in Enzyme does a shallow rendering to the DOM. Shallow rendering does not render any components nested within the Welcome component. The find method in Enzyme accepts jQuery-like selectors to retrieve a node from the DOM tree. In the above test, the text of the div node is retrieved and asserted to equal 'Hello world'.
Apart from shallow rendering, Enzyme allows full rendering using the mount function and HTML rendering using the render function. Shallow rendering is used to isolate the component for unit testing.

Testing the structure

We will create a List component which displays a list of items in tabular format. The React component under test is shown below.

export default class List extends React.Component {
  render() {
    return (
      <table className="myClass">
        <thead>
          <tr>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {this.props.data.map((item, index) => (
            <tr key={index}>
              <td>
                {item}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    )
  }
}

We will test if the React component renders a table and the data passed in props.

Our test suite will be named after the React component (List). We will initialize all tests in the test suite with a beforeEach function. The beforeEach function uses the shallow function to render the component.

describe('List', () => {
  let list;

  beforeEach(() => {
    list = shallow(<List data={['Name 1', 'Name 2', 'Name 3']} />);
  });

});

The find method supports jQuery selectors to retrieve specific nodes from the tree. To verify if a table is rendered, we use the 'table' selector. To verify if a specific class is rendered, we use the '.myClass' selector.

it('List renders table', () => {
  expect(list.find('table').length).toEqual(1);
});

it('Class of rendered table', () => {
  expect(list.find('.myClass').length).toEqual(1);
});

The find method returns an array of nodes. To retrieve the first element, we use the first method. To retrieve the nth element, we use the at method.

it('List renders column', () => {
  const arr = list.find('th');
  expect(arr.length).toEqual(1);
  expect(arr.first().text()).toEqual('Name');
});

it('List renders data', () => {
  const arr = list.find('td');
  expect(arr.length).toEqual(3);
  expect(arr.at(1).text()).toEqual('Name 2');
});

Testing the behavior

We will create an Add component which has a form element with an input text and a button. We want to test the behavior of the component when the button is clicked. The code for Add component is shown below.

export default class Add extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        name: ''
      };
      this.handleAdd = this.handleAdd.bind(this);
    }

    handleAdd(e) {
        e.preventDefault();
        this.props.onAdd(this.state.name);
    }

    render() {
      return (
        <form>
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={e => this.setState({ name: e.target.value })}
          >
          </input>
          <button onClick={this.handleAdd}>Add</button>
        </form>
      );
    }
}

The Add component calls the handleAdd method when the button is clicked. The handleAdd method calls the onAdd function set in the props. Usually, the actual work is performed by the parent component or a Redux action.

Testing behaviour is best performed by mounting the component. Jest has good mocking support. We will use jest.fn() to create a mock function. We will supply the mock to the onAdd prop of our component.

describe('Add', () => {
  let add;
  let onAdd;

  beforeEach(() => {
    onAdd = jest.fn();
    add = mount(<Add onAdd={onAdd} />);
  });

});

We will start testing by checking whether the onAdd prop is defined. We will also test the structure by checking if the button is rendered.

it('Add requires onAdd prop', () => {
  expect(add.props().onAdd).toBeDefined();
});

it('Add renders button', () => {
  const button = add.find('button').first();
  expect(button).toBeDefined();
});

We will then test the behavior of the component when the button is clicked. When the button is clicked, our mock should be called with the appropriate arguments.

it('Button click calls onAdd', () => {
  const button = add.find('button').first();
  const input = add.find('input').first();
  input.simulate('change', { target: { value: 'Name 4' } });
  button.simulate('click');
  expect(onAdd).toBeCalledWith('Name 4');
});

Enzyme does not allow to change the value of the input element. Instead, we simulate the change event on the input text. After that, we simulate the click event of the button. The simulate method is useful to simulate user actions.

There is a reason why I recommended the mount function instead of the shallow function. With shallow rendering, we have to pass the event arguments along with the simulated event. In the above scenario, simulating the click event will require event arguments to be passed. The handleAdd method in our React component calls the eventArgs.preventDefault() method. Without passing any event arguments, the test will fail.

With shallow rendering, props on the component cannot be tested. The call to ShallowWrapper.props().propName will return undefined . These are limitations with shallow rendering. These issues could be fixed in future releases.

Testing the nested components

We will embed the Add and the List component we created in the last few sections into a new component called App. The code for the App component is shown below.

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: []
    };
    this.handleAdd = this.handleAdd.bind(this);
  }

  handleAdd(name) {
    const list = this.state.list.slice();
    list.push(name);
    this.setState({ list });
  }

  render() {
    return (
      <div>
        <Add onAdd={this.handleAdd} />
        <List data={this.state.list} />
      </div>
    );
  }
}

We will test the App component for structure and behavior. The App component should contain both the Add and the List component. When the onAdd event of the Add component is triggered, the List component receives a new data prop.

We use shallow function to shallowly render the component. This does not render the nested components.

describe('App', () => {
  let app;

  before(() => {
    app = shallow(<App />);
  });
});

We test the structure of the App component using the find method. The selector for the inner component is the component name. The code below tests if the component contains the two inner components, Add and List.

it('App renders nested components', () => {
  expect(app.find('Add').length).toEqual(1);
  expect(app.find('List').length).toEqual(1);
});

We want to test component interactions between nested components. When the onAdd event is triggered within the Add component, the List component should be updated with a new entry.

it('onAdd updates List', () => {
  const add = app.find('Add').first();
  add.props().onAdd('Name 1');
  app.update();     
  const list = app.find('List').first();
  const listData = list.props().data;
  expect(listData.length).toEqual(1);
  expect(listData[0]).toEqual('Name 1');
});

We get the Add component using the find method. We trigger the onAdd event. We allow the component to update itself. After the update, the list component has the newly added entry.

API

The API disclosed by Jest and Enzyme are very different.

Jest API

The Jest API focusses more on the ability to define tests, make assertions, and create mocks.

  • describe: defines a test suite.
  • it: defines a test.
  • beforeEach: defines an entry hook before running each test.
  • expect: makes an assertion.
  • jest.fn(): creates a mock function.

Several methods are available for assertions.

  • toEqual: checks if two objects have the same value.
  • toBe: checks if two objects have the same value and type.
  • toBeDefined: checks if the object is defined.
  • toContain: checks if an item is present in a list.
  • toBeCalled: checks if a mock function is called.

There are useful methods on the mock.

  • mockImplementation provides an implementation for the mock function.
  • mockReturnValue returns a value when the mock function is called.

Enzyme API

Enzyme API focusses on rendering the react component and retrieving specific nodes from the rendered tree. There are three ways to render a component.

  • shallow: renders only the component under test. Dependent components are not rendered.
  • mount: mounts the full component in JSDOM.
  • render: renders the component as static HTML.

There are several methods to retrieve nodes from the rendered component.

  • find: accepts a selector and retrieves nodes that match the selector.
  • findWhere: retrieve nodes selected by the predicate.
  • some: returns true if there is at-least one node matching the selector.
  • someWhere: returns true if there is at-least one node selected by the predicate.
  • first: returns the first node of a set.
  • at: returns the nth node of a set.
  • html: gets the HTML of the node.
  • text: gets the text representation of the node.

There are more methods to interact with the component and retrieve the component state.

  • simulate: simulates an event.
  • setProps: sets the props.
  • setState: sets the state.
  • setContext: sets the context.
  • prop(key): retrieves prop value corresponding to the provided key.
  • state(key): retrieves state corresponding to the provided key.
  • context(key): retrieves context value corresponding to the provided key.

Conclusion

We have seen how to use Enzyme with Jest in this tutorial. Enzyme has a simple API to test React components. Jest is a unit test framework designed by Facebook to test react applications. Jest allows to write unit tests using the Snapshot feature. Enzyme allows to write unit tests using regular assertions. Using Enzyme with Jest makes writing tests for React applications a lot easier.

There is an accompanying github project which has the source code explained in this tutorial.

Other useful resources

If you're still confused between the two after this tutorial, here are more resources around the web to help you:

Discover and read more posts from Vijay Thirugnanam
get started
post comments10Replies
Alex Howez
6 years ago

Excellent post! Thanks

Balaji Birajdar
7 years ago

That is a brilliant article. I was able to setup tests for my React App using this. However, I have a problem testing the nested components.
I have nested components, where other components are referred by a namespace alias rather than the physical path of the child component. The enzyme test framework is unable to resolve the alias and gives 'Cannot find the module ‘…childComponent name here’;

I have posted a detailed description of my issue here https://www.codeproject.com/Questions/1222693/How-to-resolve-aliases-defined-for-paths-when-unit

jose heriberto perez magaña (elh)
7 years ago

Thanks for the post Vijay, it is pretty complete and pretty well written, I’m trying to setup my test suite and for simple components it works pretty good, the problem occurs when I try to use scss(sass), I’m getting Invalid or unexpected token error when I try to import any sass file to be more specific I got this:

 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){@import "../../styles/variables";

SyntaxError: Invalid or unexpected token

Do you have experience using sass in components and testing them?

Show more replies