dribbblefacebookgithubgooglepluslinkedinrsstwitter

Testing React Components with Enzyme

Posted on by Devin Clark

We finally boarded the React train and it is great. I looked at Enzyme and Jest. Enzyme seems a lot easier to use so I chose it. I am impressed with how it turned out.

For this workflow, you will need to install these packages.

npm install enzyme babel-register babel-preset-react chai mocha react-addons-test-utils --save-dev

This is the relevant part of the package.json file. The first argument to the mocha command is a setup file for mocha. The second is a glob for all the test files. I am using an npm script for this but use whatever.

"scripts": {
  "test": "mocha website/test/.setup.js website/test/**/*-test.js"
}

Here is the .setup.js file. It adds babel-register with the react preset. This allows JSX to work in the tests.

require("babel-register")({
  presets: ["react"]
});

Here we have a small stateless react component that we are going to test. Stateless React components are pure functions without internal state or lifecycle methods. You should use these anywhere you are not using state or any of the component lifecycle methods. Pure functions are easier to test. Given the same input, a pure function always return the same output.

var React = require('react');

function Burrito(props) {
  'use strict';

  return (<div className={props.name}>{props.ingredients && props.ingredients.join(', ')}</div>);
}

Burrito.defaultProps = {
  name: 'burrito'
  ingredients: []
};

Burrito.propTypes = {
  name: React.PropTypes.string,
  ingredients: React.PropTypes.array
};

module.exports = Burrito;

Now, let's test this component. Enzyme allows us to shallow render a component, then exposes an API to get and set data on the rendered component. My favorite part is it doesn't use PhantomJS. We are going to use .text() and .hasClass(classname) in this example.

var wrapper = enzyme.shallow(<Burrito />);
expect(wrapper.text()).to.equal('');

We did it! In this test, we shallow render our Burrito component, then we expect (a chai function) the text of the rendered component to be empty.

We can also do something similar with hasClass.

var wrapper = enzyme.shallow(<Burrito />);
expect(wrapper.hasClass('burrito')).to.equal(true);

Here is a full example for testing the Burrito component we created.

var React = require('react');
var expect = require('chai').expect;
var enzyme = require('enzyme');
var Burrito = require('../js/components/Burrito');

describe("<Burrito />", function() {
  'use strict';

  describe('Text: ', function () {
    it("renders no text with no props", function() {
      var wrapper = enzyme.shallow(<Burrito />);
      expect(wrapper.text()).to.equal('');
    });

    it("renders no text with no props", function() {
      var wrapper = enzyme.shallow(<Burrito ingredients={['chicken', 'beans']} />);
      expect(wrapper.text()).to.equal('chicken, beans');
    });
  });

  describe('Name: ', function() {
    it("is named 'burrito' with no props", function() {
      var wrapper = enzyme.shallow(<Burrito />);
      expect(wrapper.hasClass('burrito')).to.equal(true);
    });

    it("is named 'the-james-mason' with 'the-james-mason' passed as name", function() {
      var wrapper = enzyme.shallow(<Burrito name='the-james-mason' />);
      expect(wrapper.hasClass('the-james-mason')).to.equal(true);
    });

  });
});

Test coverage is another cool thing I did. I used nyc to generate a coverage report after the tests run. The coverage report shows you what percentage of each file's statements, branches, and functions are tested. To run nyc, you can change the mocha npm script to look the code below.

"scripts": {
  "test": "nyc mocha website/test/.setup.js website/test/**/*-test.js"
}

It seems a bit magical to me but it did help me uncover missing paths in my tests.

Writing tests like these for all your components gives an incredible peace of mind. Generating test coverage will help ensure we are testing everything. I may try Ava instead of Mocha in the future.