Testing is an important part of the development cycle. There is a name for code without tests: legacy code. When I first started learning React and JavaScript it was overwhelming to say the least. If you’re new to the JS/React community you’re probably feeling super overwhelmed as well. Thinking things like:

Which build tool should I use?

Which testing framework is right?

What flux pattern should I be learning?

Do I even need to use flux?

That’s why I decided to write this post. After hours of reading blog posts, looking through source code of respected JS developers, and one JSConf in Florida, I’ve finally found my testing ‘groove’. In the beginning I felt so naked and dirty for programming React code without tests. I want to live in a world where no developer ever needs to feel that way. It’s just not right.

All the code for this tutorial is available on my github located here.

Let’s get started!

Setting Up Webpack 🔗

This isn’t a tutorial on how to use webpack so I won’t go into great detail but it’s important to understand the basics. Webpack is like the Rails Asset pipeline on crack. On a basic level it lets you pre/post process all your code and serve just one bundle.js to the client which will run your react app.

It’s an extremely powerful tool which is why we’ll be using it. Webpack is one of those tools that scares the shit out of you at first, which I recommend you embrace, but once you start understanding how it works you feel like a God. Stick with it and give it a chance before you judge.

We often don’t like things we’re not good at or scared of. However, once you overcome the initial discomfort and start understanding something it almost always becomes fun. In fact, that’s exactly how I felt about testing in general. Hated it when I sucked at it, love it now that I don’t suck at it 😃

Here are some more resources to learn more about webpack if interested:

warning I recommend using node v5.1.0 if you’re going to follow along with this tutorial. Anything >4 should be fine though.

First, lets install all webpack and babel dependencies. Babel is a JavaScript transpiler which allows us to write ES6 (es2015) and ES7 code and make sure it all gets compiled down to ES5 which the browser can use.

mkdir tdd_react cd tdd_react npm init # follow along with normal npm init to set up project npm i babel-loader babel-core webpack --save-dev

add_alert npm i is an alias for npm install.

Next lets set up our project directory and create a webpack.config.js file:

mkdir src touch src/main.js mkdir test mkdir dist touch webpack.config.js

Our initial webpack config will be super minimal. Read through comments to understand what’s going on:

var webpack = require ( 'webpack' ); var path = require ( 'path' ); var config = { entry : [ './src/main.js' ], output: { path : path.resolve(__dirname, 'dist' ), filename: 'bundle.js' }, module : { loaders : [ { test : /\.js$/ , loaders : [ 'babel' ], exclude : /node_modules/ } ] } } module .exports = config;

In order for babel to do its job properly we need to tell it which presets we want to use. Let’s go ahead and install the React and ES6 presets since we’re going to need those:

npm i babel-preset-react babel-preset-es2015 --save-dev

Now we have a couple options. Some people will tell babel to use the presets in the loader directly in the webpack config file like this:

loaders: [ { test : /\.js$/ , loaders : [ 'babel' ], exclude : /node_modules/ , query : { presets : [ 'react' , 'es2015' ] } } ]

Another approach is to store them in a .babelrc which is what I’m going to do for this project. By storing our babel presets in the .babelrc it makes it easier for future developers to find what kind of babel presets are enabled. Additionally, when we set up Karma to use webpack later in the tutorial we won’t need to do any preset configuration since it will already be present in the .babelrc file.

touch . babelrc

Paste in the presets:

# .babelrc { "presets": ["react", "es2015"] }

info Due to the nature of how quickly npm packages get upgraded in the React/JS community. If at any point in the tutorial you’re getting errors try installing exact package versions using the package-name@version-number-here syntax. The completed package.json can be found at the end of the tutorial for reference

To confirm it works lets put some react code in main.js and see if it bundles everything properly. Install React and React DOM:

npm i react react-dom -S

add_alert Using the -S flag is an alias for --save .

Create the first React component:

src/main.js

import React, { Component } from 'react' ; import { render } from 'react-dom' ; class Root extends Component { render() { return < h1 > Hello World </ h1 > ; } } render( < Root /> , document.getElementById('root'));

The astute reader will have noticed we have not yet created an index.html file with our root. Lets do that next and put it in our /dist folder since that is where our bundle.js will get compiled to:

# /dist/index.html < html > < head > < meta charset = "UTF-8" /> </ head > < body > < div id = "root" > </ div > < script src = "bundle.js" > </ script > </ body > </ html >

Awesome. Making progress. Finally we can run webpack and see if everything worked properly. If you don’t have webpack installed globally ( npm i webpack -g ) then you can run it from your node modules like so:

./node_modules/.bin/webpack

Webpack will by default look for a config with the name webpack.config.js . You could also pass in a different webpack config as an argument if you so pleased.

Let’s create an alias for doing the build inside our package.json:

# package.json ... other stuff "scripts": { "build": "webpack" }

Next lets wire up webpack-dev-server for a more enjoyable dev experience:

npm i webpack-dev-server --save-dev

Add webpack dev server entry points to our webpack.config.js

... rest of config entry: [ 'webpack/hot/dev-server' , 'webpack-dev-server/client?http://localhost:3000' , './src/main.js' ], ... rest of config

Make script for running the dev server:

... other stuff scripts: { "dev" : "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist" , "build" : "webpack" }

The script uses the --content-base flag to tell webpack we want to serve our /dist folder. We’re also explicitly using port 3000 to provide a more familiar Rails experience.

Finally, lets add a resolve flag to webpack to make importing files a little more intuitive. Here is the final config with comments, read through them:

var webpack = require ( 'webpack' ); var path = require ( 'path' ); var config = { entry : [ 'webpack/hot/dev-server' , 'webpack-dev-server/client?http://localhost:3000' , './src/main.js' ], resolve : { root : [ path.resolve(__dirname, './src' ) ], extensions: [ '' , '.js' , '.json' , '.jsx' ] }, output : { path : path.resolve(__dirname, 'dist' ), filename : 'bundle.js' }, module : { loaders : [ { test : /\.js?$/ , exclude: /(node_modules|bower_components)/ , loader: 'babel' } ], } } module .exports = config;

To confirm everything works lets go ahead and run the dev server and confirm we see ‘Hello World’ on the screen.

npm run dev open http:

You should see something that looks like this:

Setting Up Mocha, Chai, Sinon, and Enzyme 🔗

Mocha: will be used for running our tests.

Chai: will be used as our expectation library. Very versatile and lets us use RSpec like syntax.

Sinon: will be used for mocks/stubs/spys.

Enzyme: will be used for testing our React components. It’s a beautiful testing library written by AirBnB.

Install packages:

npm i mocha chai sinon -- save - dev

If we want to be able to write tests in ES6 we will need to compile the tests before running. To do that we can use babel-register:

npm i babel-register -- save - dev

Let’s add some npm scripts in package.json to make testing life easier:

# ./ package .json ... rest of package .json "scripts" : { "test" : "mocha --compilers js:babel-register --recursive" , "test:watch" : "npm test -- --watch" , "build" : "webpack" , "dev" : "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist" , },

Our test script says to run mocha using the babel-register compiler and look recursively through our /test directory.

Eventually we’re going to set up Karma so these npm scripts will be useless but if Karma isn’t your thing then those should work fine. npm run test:watch will watch for file changes and re-run your suite. Hooray productivity!

To confirm it works lets create a hello world test in /tests/helloWorld.spec.js

# /test/helloWorld.spec.js import { expect } from 'chai'; describe('hello world', () => { it('works!', () => { expect(true).to.be.true; }); });

Pretty chill… almost looks like RSpec!

Importing expect for every test is kind of a bummer though so lets create a test_helper file to save keystrokes.

# /test/test_helper.js import { expect } from 'chai'; import sinon from 'sinon'; global.expect = expect; global.sinon = sinon;

Then include it in the npm script that runs the suite via the --require ./test/test_helper.js statement:

# package.json script section "test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive",

I also added sinon so it would be available globally. Now whenever we write new tests expect and sinon will be available to use and we won’t need to import them manually.

Now that our more ‘universal’ testing tools are set up (mocha, chai, sinon) let’s install Enzyme and start testing some React components!

Install packages:

warning It’s important to install version 1.2.0 of enzyme since in 2.0 there are some breaking changes that affect the tutorial

npm i enzyme @ 1 . 2 . 0 react-addons-test-utils --save-dev

Enzyme has great documentation which can be found here. I recommend reading through the Shallow Rendering section when you have the time.

What is Shallow Rendering you ask?

Well, it’s a way for us to call the render method of our components and get back React elements which we can make assertions on without having to actually mount our component to the DOM. For more on React Elements see here.

Enzyme will wrap the shallow rendered component in a special wrapper which we can then test. Think of it like the page object in Capybara if you come from Rails.

Lets do some TDD to drive the development of a proper <Root /> component.

This Root component will be a container meaning that it will be in charge of handling the state of our application. Learning the difference between smart and dumb components in React is very important for good application architecture. Here is a great blog post explaining what they are.

# /tests/containers/Root.spec.js import React from 'react'; // required to get test to work. we can get around this later with more configuration import { shallow } from 'enzyme'; // method from enzyme which allows us to do shallow render import Root from '../../src/containers/Root'; // import our soon to be component describe('(Container) Root', () => { it('renders as a <div>', () => { const wrapper = shallow(<Root />); expect(wrapper.type()).to.eql('div'); }); it('has style with height 100%', () => { const wrapper = shallow(<Root />); const expectedStyles = { height: '100%', background: '#333' } expect(wrapper.prop('style')).to.eql(expectedStyles); }); it('contains a header explaining the app', () => { const wrapper = shallow(<Root />); expect(wrapper.find('.welcome-header')).to.have.length(1); }); });

If we run the tests with npm test they should fail. It makes sense since we havn’t actually created a Root component in the proper location. So lets do that:

info If at any point you want to see the source for this code it is all available on github here

# /src/containers/Root.js import React, { Component } from 'react'; const styles = { height: '100%', background: '#333' } class Root extends Component { render() { return ( <div style={styles}> <h1 className='welcome-header'>Welcome to testing React!</h1> </div> ) } } export default Root;

If you re-run the tests they should all pass now.

There was a lot of duplication in our tests so lets go back and do some refactoring. Since we’re never passing any props to the Root , we can just shallow render it once and then make all our assertions off that one wrapper. Often times I find myself wrapping a section of tests in ‘sub’ describe blocks that pass in a certain set of props and then make a bunch of assertions given those props. Similar to using ’context’ blocks if you’ve used RSpec.

describe( '(Container) Root' , () => { const wrapper = shallow(<Root />); it('renders as a <div>', () => { expect(wrapper.type()).to.eql('div'); }); it('has style with height 100%', () => { const expectedStyles = { height: '100%', background: '#333' } expect(wrapper.prop('style')).to.eql(expectedStyles); }); it('contains a header explaining the app', () => { expect(wrapper.find('.welcome-header')).to.have.length(1); }); });

Stick to using shallow as much as you possibly can in your tests. Occasionally it’s not possible though. For example, if you need to test the React lifecycle methods then you need the component to actually mount.

Next lets test a component mounting and calling a function when it mounts so we can get some exposure to sinon and using spys.

We can pretend that the Root component has a child called CommentList which will call some arbitrary callback when it mounts. The function it calls when it mounts will be given via props so we can practice testing that scenario. Let’s also conditionally render some styles on the comment list so we can see how to deal with styling in shallow renders. CommentList will go in a components folder located at /src/components/CommentList.js . Since it won’t be in charge of handling data , thus working entirely off of props, the component will be a pure (aka dumb) component:

import React from 'react' ; import CommentList from '../../src/components/CommentList' ; import { describeWithDOM, mount, shallow, spyLifecycle } from 'enzyme' ; describe( '(Component) CommentList' , () => { describeWithDOM( 'Lifecycle methods' , () => { it( 'calls componentDidMount' , () => { spyLifecyle(CommentList); const props = { onMount : () => {}, isActive: false } mount(<CommentList {...props} />); // CommentList's componentDidMount should have been // called once. spyLifecyle attaches sinon spys so we can // make this assertion expect( CommentList.prototype.componentDidMount.calledOnce ).to.be.true; }); it('calls onMount prop once it mounts', () => { // create a spy for the onMount function const props = { onMount: sinon.spy() }; // mount our component mount(<CommentList {...props} />); // expect that onMount was called expect(props.onMount.calledOnce).to.be.true; }); }); });

There is a lot going on there. Read through the comments to get a better understanding. Look at the implementation that makes the tests pass then go back and look at the tests again to confirm you understand.

# /src/components/CommentList.js import React, { Component, PropTypes } from 'react'; const propTypes = { onMount: PropTypes.func.isRequired, isActive: PropTypes.bool }; class CommentList extends Component { componentDidMount() { this.props.onMount(); } render() { return ( <ul> <li> Comment One </li> </ul> ) } } CommentList.propTypes = propTypes; export default CommentList;

Run npm test and the suite should now pass.

Next lets add some shallow rendered tests to make sure our component is applying the proper CSS classes given its isActive prop.

... previous tests it( 'should render as a <ul>' , () => { const props = { onMount : () => {} }; const wrapper = shallow(<CommentList {...props} />); expect(wrapper.type()).to.eql('ul'); }); describe('when active...', () => { const wrapper = shallow( // just passing isActive is an alias for true <CommentList onMount={() => {}} isActive /> ) it('should render with className active-list', () => { expect(wrapper.prop('className')).to.eql('active-list'); }); }); describe('when inactive...', () => { const wrapper = shallow( <CommentList onMount={() => {}} isActive={false} /> ) it('should render with className inactive-list', () => { expect(wrapper.prop('className')).to.eql('inactive-list'); }); }); });

To make them pass:

class CommentList extends Component { componentDidMount() { this .props.onMount(); } render() { const { isActive } = this .props; const className = isActive ? 'active-list' : 'inactive-list' ; return ( < ul className = {className} > < li > Comment One </ li > </ ul > ) } }

At this point you should have a good understanding on how to go about testing your react components. Remember to read through the amazing Enzyme documentation to get some inspiration.

Setting Up Karma 🔗

Setting up Karma can be somewhat difficult. I’ll openly admit it was a pain for me to get working. Usually when I develop production React apps I use a pre-built starter kit that has all the bells and whistles I know and love. This is the production ready starter kit I highly recommend.

The value in using Karma is fast test reloads, multiple browser testing, and most importantly webpack preprocessing. Once we get Karma set up we will be able to run our tests through not just a babel-loader but ALSO our entire webpack config. This will provide a ton of convenience and make our testing environment feel the same as our dev environment which is always a positive.

Lets get down to business…

npm i karma karma-chai karma-mocha karma-webpack -- save - dev npm i karma-sourcemap-loader karma-phantomjs-launcher -- save - dev npm i karma-spec-reporter -- save - dev npm i phantomjs -- save - dev # The polyfills arn't required but will help with browser support issues # and are easy enough to include in our karma config that I figured why not npm i babel-polyfill phantomjs-polyfill -- save - dev

A lot of packages, I know. Trust me getting this beast tuned in is SO worth it.

For our example we are going to be using PhantomJS. No real reason other than it’s what I’m used to using in the starter kit. Feel free to use a Chrome, Firefox, or Safari launcher instead or even on TOP of PhantomJS (one of the cool things about Karma 😉

Before going over the massive karma config install yargs to let you use command line arguments to customize the Karma config a bit.

npm i yargs -S

Now we will be able to pass in a flag to the Karma config to make it watch our files for changes and re-run the suite on save SUPER fast. Hooray productivity!

Karma Config:

touch karma .config .js

var argv = require ( 'yargs' ).argv; var path = require ( 'path' ); module .exports = function ( config ) { config.set({ browsers: [ 'PhantomJS' ], singleRun: !argv.watch, frameworks: [ 'mocha' , 'chai' ], reporters: [ 'spec' ], files: [ 'node_modules/babel-polyfill/dist/polyfill.js' , './node_modules/phantomjs-polyfill/bind-polyfill.js' , './test/**/*.js' ], preprocessors : { [ './test/**/*.js' ]: [ 'webpack' , 'sourcemap' ] }, webpack: { devtool : 'inline-source-map' , resolve : { root: path.resolve(__dirname, './src' ), extensions: [ '' , '.js' , '.jsx' ], alias: { 'sinon' : 'sinon/pkg/sinon' } }, module : { noParse: [ /node_modules\/sinon\// ], loaders: [ { test : /\.js?$/ , exclude : /node_modules/ , loader : 'babel' }, ], }, externals: { 'jsdom' : 'window' , 'cheerio' : 'window' , 'react/lib/ExecutionEnvironment' : true , 'react/lib/ReactContext' : 'window' }, }, webpackMiddleware : { noInfo : true }, plugins: [ 'karma-mocha' , 'karma-chai' , 'karma-webpack' , 'karma-phantomjs-launcher' , 'karma-spec-reporter' , 'karma-sourcemap-loader' ] }); };

Read through all the comments once or twice to get a better idea of what this config is doing. One of the beautiful things about Webpack is because it’s all just normal JavaScript code we could ‘refactor’ our config files. In fact, that’s what most starter kits end up doing!

With Karma set up the last thing we have to do is update our package.json with new scripts to run the tests:

# package.json "scripts" { "test": "node_modules/.bin/karma start karma.config.js", "test:dev": "npm run test -- --watch", "old_test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive", "old_test:watch": "npm test -- --watch" }

I renamed the old test scripts to have a prefix of ‘old_’.

The final package.json looks like this:

{ "name" : "react-testing-starter-kit" , "version" : "0.1.0" , "description" : "React starter kit with nice testing environment set up." , "main" : "src/main.js" , "directories" : { "test" : "tests" , "src" : "src" , "dist" : "dist" }, "dependencies" : { "react" : "^0.14.6" , "react-dom" : "^0.14.6" , "yargs" : "^3.31.0" }, "devDependencies" : { "babel-core" : "^6.4.0" , "babel-loader" : "^6.2.1" , "babel-polyfill" : "^6.3.14" , "babel-preset-es2015" : "^6.3.13" , "babel-preset-react" : "^6.3.13" , "babel-register" : "^6.3.13" , "chai" : "^3.4.1" , "enzyme" : "^1.2.0" , "json-loader" : "^0.5.4" , "karma" : "^0.13.19" , "karma-chai" : "^0.1.0" , "karma-mocha" : "^0.2.1" , "karma-phantomjs-launcher" : "^0.2.3" , "karma-sourcemap-loader" : "^0.3.6" , "karma-spec-reporter" : "0.0.23" , "karma-webpack" : "^1.7.0" , "mocha" : "^2.3.4" , "phantomjs" : "^1.9.19" , "phantomjs-polyfill" : "0.0.1" , "react-addons-test-utils" : "^0.14.6" , "sinon" : "^1.17.2" , "webpack" : "^1.12.11" , "webpack-dev-server" : "^1.14.1" }, "scripts" : { "test" : "node_modules/.bin/karma start karma.config.js" , "test:dev" : "npm run test -- --watch" , "build" : "webpack" , "dev" : "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist" , "old_test" : "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive" , "old_test:watch" : "npm test -- --watch" }, "repository" : { "type" : "git" , "url" : "tbd" }, "author" : "Spencer Dixon" , "license" : "ISC" }

With the addition of webpack preprocessing in our test suite we can now remove those annoying relative path declarations inside our tests:

import React from 'react' ; import { shallow } from 'enzyme' ; import Root from 'containers/Root' ; import React from 'react' ; import CommentList from 'components/CommentList' ; import { describeWithDOM, mount, shallow, spyLifecycle } from 'enzyme' ;

To use this starter kit in development now all you need to do is run:

npm run dev # note the addition of run npm run test:dev # note the addition of run

Make sure to check out the original source code on github if anything was unclear.

We have now set up a solid testing environment that can grow and evolve to your projects specific needs. In the next blog post I will spend more time going over specific testing scenarios and how to test Redux, my preferred flux implementation.

I’ve only been programming in React for a few months but I’m already in love. I hope this tutorial has helped you to get a deeper understanding of some React testing best practices. Feel free to reach out to me with any questions/comments. Test on my friends!