How to test react js components using enzyme, mocha & Babel

React js components are well designed with testing in mind – it’s just a simple matter of leveraging the right testing tools for your needs. For this how to guide, we’re going to use an established BDD test suite: Mocha, chai, sinon, jsdom, and a library specifically targeted for react js – enzyme.

Following true in React js convention, we’re going to use some ES2015 syntax, so we’ll be using the Babel JS compiler to compile it into ES5 for browser compatibility. We’re also going to use Common JS modules, which again need to be converted into a form that’s compatible with the browser, so we’ll use webpack to package the code up for us.

Let’s get on with crafting a very simple app consisting of just one component. Even so, this will give us enough code to demonstrate the build and testing process.

I’ve also accompanied this blog post with a Github testing react js components repo.

Environment expectations

  • Node.js installed (tested on v4.4.7)

Application dependencies

Let’s go ahead and create our package.json to list all the application dependencies.

{
  "private": true,
  "name": "react-js-component-testing",
  "version": "1.0.0",
  "main": "app/index.js",
  "dependencies": {
    "react": "^15.2.1",
    "react-dom": "^15.2.1"
  },
  "devDependencies": {
    "babel-core": "^6.11.4",
    "babel-loader": "^6.2.4",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.11.5",
    "babel-preset-react": "^6.11.1",
    "chai": "^3.5.0",
    "enzyme": "^2.4.1",
    "jsdom": "^9.4.1",
    "mocha": "^2.5.3",
    "react-addons-test-utils": "^15.3.0",
    "sinon": "^1.17.5",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  },
  "scripts": {
    "start": "webpack-dev-server --content-base public"
  }
}

We’ll make some amendments to this later on, but this is good for now.

Go ahead and run npm install inside of your project root directory.

Application runtime

NOTE: If you’re already comfortable with webpack and the various configuration that’s required, skip to the testing section.

Create a public directory in the project root, and add a simple index.html, like this:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>React js component testing</title>
</head>
<body>

<div id="app"></div>

</body>
</html>

For convenience, we’re going to use webpack’s dev server. Behind the scenes it’s using the node.js express server.

We need to configure webpack so that it can serve up the public directory.

Create the file webpack.config.js at the project root and enter the following content:

module.exports = {
    output: {
        path: __dirname + '/public'
    }
};

If you noticed earlier, we setup an npm start script inside package.json, which boots up the webpack dev server. Go ahead and run npm start inside your project root directory.

You can now open http://localhost:8080/ in the browser and view your HTML file.

Testable react js component

Let’s create our react js component.

Create the directories app/components/ at the project root. Inside the components directory, create the JS file Home.js with the following content:

import React from 'react';

export default React.createClass({
    render: function() {
        return (
            <div>
                <h1>Home page</h1>
                <p>{this.props.text}</p>
            </div>
        );
    },
    componentDidMount: function() {
        return;
    }
});

We’ve added a redundant componentDidMount method for demonstration purposes, as we want to access the execution of this component lifecycle method during our testing.

Let’s finish the code off by creating our straight forward entry point JS file. Create the file app/index.js with the following contents:

import React from 'react';
import ReactDOM from 'react-dom';
import Home from './components/Home';

ReactDOM.render(
    <Home text="Hello, world!"/>,
    document.getElementById('app')
);

Now we need to generate a JS payload that’s browser compatible. We’re going to use webpack to create this JS bundle for us. Part of this process involves utilising the Babel compiler.

Firstly, let’s inform webpack what Babel settings are required for the compilation. Create a .babelrc file at the project root with the following contents:

{
  "plugins": [
    "transform-es2015-modules-commonjs"
  ],
  "presets": [
    "react"
  ]
}

We are ensuring that our ES2015 modules get converted to common js modules, and that our JSX syntax gets converted into proper reactjs. We need to convert to common js modules so that webpack can create our bundle and do all the relevant boilerplating and dependency injection on our behalf.

You can find out what ES2015 Babel plugins are available here: http://babeljs.io/docs/plugins/preset-es2015/. If you’re using all the transforms, or just for convenience, you can use the es2015 preset so that you don’t have to include them all separately. You can see that we’ve used the react preset, which is just a collection of the following Babel plugins.

webpack will read .babelrc when packaging up the JS for us, but we need to provide webpack with some more configuration, in the form of a loader.

Open up webpack.config.js and update the contents to the following:

module.exports = {
    entry: './app/index.js',
    output: {
        path: __dirname + '/public',
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules|public)/,
                loader: 'babel-loader'
            }
        ]
    }
};

The loader (babel-loader) is officially provided by Babel to easily integrate with webpack. We installed this as a dependency at the beginning.

That’s all the config setup for this part.

You’ll notice that the output file name is bundle.js. Let’s make sure to include this JS asset into index.html. Update index.html to the following:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>React js component testing</title>
</head>
<body>

<div id="app"></div>

<script src="bundle.js"></script>
</body>
</html>

Go back into the console and stop the webpack dev server from running (Ctrl + C). Then run npm start again so that it picks up the latest configuration.

Refresh http://localhost:8080/ in the browser and wait for it to load.

During this initial refresh, webpack gets busy compiling the latest bundle again, which you’ll be able to observe from the console. This compilation happens automatically on file changes when the server is running, thanks to the implementation of webpack dev server.

Once the page has loaded, open up the dev tools and inspect the bundle.js script. You’ll be able to observe that webpack has packaged up our application code, inclusive of the vendor code (react js & react dom), and served it as a browser compatible bundle. Our ES2015 imports have been converted into common js modules, and the JSX has been converted into react js code.

Testing

Here’s what’s going to comprise our testing suite:

  • Mocha – test runner
  • Chai – with the BDD interface
  • Sinon – we’ll use the spy implementation
  • Enzyme – testing utilities for React js
  • jsdom – for an accurate headless DOM. Enzyme easily integrates with jsdom, as we’ll see

Firstly, create the directories tests/components/ from the document root. We’ve only got one component to test (app/components/Home.js), so create a corresponding test JS file (tests/components/Home.js).

For bonus points, add the tests directory to the webpack config exclusion list (exclude: /(node_modules|public|tests)/), and restart the webpack dev server if you’re still viewing the app in the browser. We don’t wish to compile anything using webpack within the tests directory. Rather, mocha will be doing this task for us – more on this later.

Within tests/components/Home.js, begin by creating the following content:

import React from 'react';
import { mount } from 'enzyme';
import Home from '../../app/components/Home';
import { expect } from 'chai';
import sinon from 'sinon';

To note here, we’ve imported mount from the enzyme testing library. This gives us access to the full rendering API, that we can use in conjunction with jsdom. Enzyme will mount the react component into the DOM provided by jsdom. There are other rendering APIs, but I like the jsdom version for its accuracy, and its capability to test component life cycle methods, such as componentDidMount.

Before we continue with writing our test, we need to set up some jsdom boilerplate, so that the mount method from enzyme works as expected.

Create the file tests/jsdom-setup.js with the following contents:

var jsdom = require('jsdom').jsdom;

var exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
    if (typeof global[property] === 'undefined') {
        exposedProperties.push(property);
        global[property] = document.defaultView[property];
    }
});

global.navigator = {
    userAgent: 'node.js'
};

This boilerplate for jsdom and enzyme in harmony can be found in some of enzyme’s documentation.

When we launch mocha, our test runner, we’ll inform it of this jsdom setup file, so that it spins up the jsdom environment prior to our tests. There’s a CLI option for this, which we’ll get onto later.

Head back inside of tests/components/Home.js and add some tests:

import React from 'react';
import { mount } from 'enzyme';
import Home from '../../app/components/Home';
import { expect } from 'chai';
import sinon from 'sinon';

describe('<Home/>', function() {
    var wrapper;

    it('calls componentDidMount once only', function() {
        // 1
        var spy = sinon.spy(Home.prototype, 'componentDidMount');
        // 2
        wrapper = mount(<Home text="Hello, world!"/>);
        // 3
        expect(spy.calledOnce).to.equal(true);
    });

    it('`props` contains a `text` property with a value of "Hello, world!"', function() {
        // 4
        expect(wrapper.props().text).to.equal('Hello, world!');
    });

    it('has an `h1` tag with the text "Home page"', function() {
        // 5
        expect(wrapper.contains(<h1>Home page</h1>)).to.equal(true);
    });

    after(function() {
        // 6
        global.window.close();
    });
});
  1. Using the sinon library, we create a spy, so that we can monitor the usage of the Home component’s componentDidMount method throughout the rendering process
  2. Using the enzyme API, by calling mount, the Home component is rendered into the jsdom environment we setup in tests/jsdom-setup.js
  3. We use the expect method from Chai to ensure the componentDidMount method was only fired once, with the help of the sinon spy API method calledOnce
  4. Inside the next test, we check that the component props object contains our text property of value Hello, world!. We use the enzyme props method to perform this check
  5. The last test searches the ReactWrapper (wrapper) for the h1 node with the text content of Home page. We utilise the enzyme contains method to perform this check
  6. The global object contains a reference to the jsdom window, so we close it down to free memory as good practice

Running the tests

With the tests all in place, it’s now just a matter of running them. As mentioned, we’re going to use mocha as our test runner.

Edit the package.json, so that you have another npm scripts property named test:

...
"scripts": {
  "start": "webpack-dev-server --content-base public",
  "test": "mocha --compilers js:babel-core/register --require tests/jsdom-setup.js tests/components/*.js"
}
...

We’ve specified some important CLI options here.

The first one is --compilers, which you can read about in the docs. The --compiler option provides a mechanism to compile the code before the tests run.

The js part before the colon maps to the file extension and babel-core/register specifies the module name, after the colon.

This usage with the Babel compiler is documented in Babel’s docs. When viewing the docs, be sure to activate the Mocha button on the page, which reveals the relevant instructions:

mocha test framework babel compiler

If you follow the instructions on the documentation instead of here, you’ll notice they reference the npm module babel-register. However, because we already have the babel-core module installed, we can point to the register.js file that resides within babel-core, which simply points to babel-register anyway.

The next important mocha CLI option is --require, which you can read about on the docs too.

This option provides a way to launch the jsdom environment just once at the start, so that it’s available within every tests file. This assumes you’re okay with the same testing environment instance being used for every test, so be confident your tests won’t leak any side effects, otherwise you could set yourself up for some unexpected test results. For our example, it meets our needs and is pretty convenient.

The final part of the command specifies which test files to run: tests/components/*.js.

Now… run the tests with npm test at the console.

You should see something like the following:

testing react js components

You’ve reached the end! Now you’ve learnt how to test your react js components using the recognised build tools (npm, babel & webpack), and the established testing suite (mocha, chai, sinon, jsdom) with the react specific testing library enzyme.

2 thoughts on “How to test react js components using enzyme, mocha & Babel

  1. Dan Z

    Man…this is a fantastic article. Should be number 1 on Google. I have read through so many poorly explained tutorials. This is the ticket. Just wish the page was Mobile responsive.

    Reply

Leave a Reply

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