In this article you will learn how to setup your environment and get started with React Unit Testing. The setup I am detailing here is inspired by this much longer yet amazing tutorial.

To get the most out of this post, it’s best if you have had some exposure to at least some of these frameworks/tools:

Project Setup

Everything we need to write our first React component.

NPM

Let’s get started with a new NPM project

$ npm init -y

Dist Folder

Create a dist folder and an dist/index.html file that will be the shell for our React code.

Here is the HTML code we will use:

<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>

We will use webpack to generate bundle.js from the React code will write later on.

Webpack & Babel

Install Webpack and Webpack’s local web server module.

$ npm install --save-dev webpack webpack-dev-server

If you have not done so, install these modules globally as well:

$ npm install -g webpack webpack-dev-server
  • webpack: base webpack module
  • webpack-dev-server: easy web server with webpack (defaults to http://localhost:8080/)

We will also need all the Babel compilers:

$ npm install --save-dev babel-core babel-loader babel-preset-react babel-preset-es2015
  • babel-core: needed to do anything with babel. Install this globally if you have not already done so.
  • babel-loader: babel plugin for webpack.
  • babel-preset-react: babel plugin to compile our React JSX files.
  • babel-preset-es2015: babel plugin to compile our ES6/ES2015 code.

We are now ready to write the first version of webpack’s configuration: webpack.config.js

var path = require('path');

module.exports = {
entry: [
'./src/index.jsx'
],
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
}]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: 'bundle.js'
},
devServer: {
contentBase: './dist'
}
};
  • module and resolve contain the Babel configuration.
  • output contains the output bundle.js configuration.
  • devserver indicates which folder should be used as the root for webpack-dev-server.

React libraries

The start of the show:

$ npm install --save react react-dom
  • react: React base module
  • react-dom: to render React component to the DOM

Package.json - Babel configuration

In your package.json, add the following section to tell Babel to use the ES6 and React plugins. This is usually done in a .babelrc file but I find it cleaner to put it in package.json

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

App code

Let’s start building !

App requirements

This is what our app does. Starting with an array of strings, our component will generate one button for each of these strings. A click on any of the generated buttons should display the button text in a label.

src/index.jsx

Let’s start with a src/index.jsx file that will hold our component:

import React from 'react';
import ReactDOM from 'react-dom';
import Component from './component';

const options = ['OK', 'No', 'Cancel'];

ReactDOM.render(
<Component options={options} />,
document.getElementById('app')
);

We have a Component React class with an options property that will hold our string array.

src/Component.jsx

Here is the code for that component, put it in src/Component.jsx:

import React from 'react';

export default React.createClass({
getOptions: function () {
return this.props.options || [];
},
getInitialState: function() {
return {
displayText: "Click on a button"
};
},
clickHandler: function (displayText) {
this.setState({displayText: displayText});
},
render: function () {
return <div>
<div className={"display-text"}>{this.state.displayText}</div>
{
this.getOptions().map(option =>
<button key={option}
onClick={() => this.clickHandler(option)}>
<span>{option}</span>
</button>
)
}
</div>
}
});
  • The initial text in our div.display-text label is "Click on a button".
  • We are generating buttons for each string in this.props.options.
  • A click on one of these buttons will update the state and the label’s text.

Pretty straightforward. At this stage, you can to run the app with:

$ webpack-dev-server

and open your browser at http://localhost:8080/.

Unit testing

Let’s now look at why we are here in the first place. Unit testing our newly created Component.

Unit testing libraries

Let’s start by installing and configuring our Unit testing environment. We will be using:

  • mocha to run our tests and chai to write them.

  • jsdom to emulate a web browser so that we won’t need a real browser to run our tests.

  • react-addons-test-utils which is the official React test helper library.

$ npm install --save-dev mocha chai jsdom react-addons-test-utils

Configure jsdom

jsdom requires some configuration that we will put in a test/test-helper.js file. Later, mocha will run this file before running any test.

We need to set up a basic HTML environment for jsdom and transfers the window object and all its properties to Node’s global object so that mocha can access them seamlessly.

import jsdom from 'jsdom';

// Setup a basic HTML document
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');

// Extract the window element from the document
const win = doc.defaultView;

// Insert these objects in Node's global object.
global.document = doc;
global.window = win;

// Take out all the properties from the window object and add them to global
Object.keys(window).forEach((key) => {
if (!(key in global)) {
global[key] = window[key];
}
});

package.json - Test script

Our Mocha command is pretty complex so let’s add a script in the the package.json.

"scripts": {
"test": "mocha --compilers js:babel-core/register --require ./test/test-helper.js 'test/**/*.@(js|jsx)'"
},

Now we can simply type npm run test to run our tests.

Details on that command:

  • 'test/**/*.@(js|jsx)': It runs all mocha unit tests in .js and .jsx files in the /test/ folder.
  • --compilers js:babel-core/register: It compiles the ES6 and React code with Babel. You might have to install babel-core globally to execute that command. Do so if you run into problems.
  • --require ./test/test-helper.js: It executes our jsdom configuration in test/test-helper.js before running the tests.

Writing the first test

And the moment we have all been waiting for: our first test in test/Component-spec.jsx.

Let’s start bu testing the button generation:

import React from 'react';
import {
renderIntoDocument,
scryRenderedDOMComponentsWithTag
} from 'react-addons-test-utils';
import Component from '../src/Component';
import {expect} from 'chai';

describe('Component', () => {

it('renders as many buttons as stated in the options property', () => {

const component = renderIntoDocument(
<Component options={['Bleach', 'Nevermind', 'In Utero']} />
);

const buttons = scryRenderedDOMComponentsWithTag(component, 'button');

expect(buttons.length).to.equal(3);
expect(buttons[0].textContent).to.equal('Bleach');
expect(buttons[1].textContent).to.equal('Nevermind');
expect(buttons[2].textContent).to.equal('In Utero');
});

});

Imports

We are going to use some of the react test utils methods:

  • renderIntoDocument: renders a component into a detached DOM.
  • scryRenderedDOMComponentsWithTag: finds all instances of components based on tag name

Test Flow

We start by rendering our Component, then retrieve all the and test that the button’s text is what we set in the options property.

Run the tests

Run this test using

$ npm run test
# 1 passing (43ms)

Writing a second test

Before we go, let’s write a second test that check that the test has changed when a button is clicked:

import React from 'react';
import {
renderIntoDocument,
scryRenderedDOMComponentsWithTag,
findRenderedDOMComponentWithClass,
Simulate
} from 'react-addons-test-utils';
import Component from '../src/Component';
import {expect} from 'chai';

describe('Component', () => {

// [...]

it('changes the displayed text when button is clicked', () => {

const component = renderIntoDocument(
<Component options={['Bleach', 'Nevermind', 'In Utero']} />
);

const displayText = findRenderedDOMComponentWithClass(component, 'display-text');
const buttons = scryRenderedDOMComponentsWithTag(component, 'button');

Simulate.click(buttons[0]);
expect(displayText.textContent).to.equal('Bleach');

Simulate.click(buttons[1]);
expect(displayText.textContent).to.equal('Nevermind');

Simulate.click(buttons[2]);
expect(displayText.textContent).to.equal('In Utero');
});

});

Imports

Check the imports: we have added two more react test utils methods:

  • findRenderedDOMComponentWithClass: which finds one unique instance of an element based on its css class.
  • Simulate: simulate user interaction, here Simulate.click.

Test Flow

We start by rendering our Component, then retrieve all the <buttons> and the label with the display text.
For each button we simulate a click and check that the text content of the label has changed and matched the button’s text.

Run the tests

Run both our tests using:

$ npm run test
# 2 passing (50ms)

Hope you found this tutorial useful, I sure had a great time writing it :)