Test Driven React Tutorial

Jan 11th, 2016

21 min read

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.

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:

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.

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

mkdir src # where all our source code will live
touch src/main.js # this will be the entry point for our webpack bundlingmkdir test # place to store all our testsmkdir dist # this is where the bundled javascript from webpack will go
touch webpack.config.js # our webpack configuration file

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.

# inside our project root
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:

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:

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:

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

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

npmienzyme@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.

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.

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';
// Once we set up Karma to run our tests through webpack// we will no longer need to have these long relative pathsimport CommentList from'../../src/components/CommentList';
import {
describeWithDOM,
mount,
shallow,
spyLifecycle
} from'enzyme';
describe('(Component) CommentList', () => {
// using special describeWithDOM helper that enzyme// provides so if other devs on my team don't have JSDom set up// properly or are using old version of node it won't bork their test suite//// All of our tests that depend on mounting should go inside one of these// special describe blocks
describeWithDOM('Lifecycle methods', () => {
it('calls componentDidMount', () => {
spyLifecyle(CommentList);
const props = {
onMount: () => {}, // an anonymous function in ES6 arrow syntax
isActive: false
}
// using destructuring to pass props down// easily and then mounting the component
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.

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!

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:

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!

tutorial

testing

react

javascript

Can I be honest? I want your email.

I love teaching and writing new content but sometimes find it hard to justify.

Getting your email motivates me to spend more time creating awesome content and notifies you when new posts or screencasts come out.

I will never share your email or spam. Expect less than 5 emails a year and feel free to unsubscribe at any time.