How to: Unit Testing React Components

Write code held to expectations.

Unit testing is a great way to define what functionality the things we build have to fulfill. It allows us some time and an environment to reason the component structure to be as atomic as possible. As a bonus, I’ve found it great at pushing me to have more functional code when doing React. To get started with unit testing in React, we’re going to use Karma, Jasmine and Enzyme.

Just interested in the example code? Head over to https://github.com/aktof/example-react-unit-testing.


What are Karma, Jasmine and Enzyme?

To run and write assertions for our tests we’re going to these three libraries. They define the different aspects of our testing environment.

Karma is a test runner that searches for our test files, process them and runs our assertions.

Jasmine is our assertion library, which is basically what it sounds like — it simply asks “did we get what we expected?”. It provides us with functions such as describe , expect and it as well as spies for checking whether a function/method was invoked.

Enzyme is a library of testing utilities for React. Enzyme provides methods to render and traverse components so we can test assertions that deal with React rendering, mounting and events.

Keep in mind, this isn’t the only way to get to unit testing. There are many different libraries and configurations that accomplish the same thing.


Our Example Application

For our tutorial we’re going to be creating a dumb Button component. The specifications are that it receives and renders the label prop we pass to it and that it calls the onClick prop when it’s clicked.

What our directory structure looks like:

lib/
src/
-- components/
----button/
------index.js
------spec.js
-- index.js
karma.conf.js
package.json
webpack.config.js

Our entry file which webpack will start out from:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Button from 'components/button';
const App = () => (
<Button
label='Replicator'
onClick={() => console.log('Tea, Earl Grey, Hot.')} />
);
ReactDOM.render(
<App />,
document.getElementByID('root'),
);

Our Button imported in entry:

// src/components/button/index.js
import React from 'react';
export default () => null;

To actually build our code we need to configure Webpack to find our entry point, set an output and tell it it how to compile the code it runs into. Below is the most basic configuration that allows it to parse our React code.

A minimal Webpack configuration:

// webpack.config.js
var path = require('path');
module.exports = {
entry: [
path.resolve(__dirname, 'src'),
],
output: {
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'lib'),
},
module: {
loaders: [
{
exclude: /node_modules/,
loader: 'babel',
test: /\.js$/,
},
{
loader: 'json',
test: /\.json$/,
},
],
},
resolve: {
root: path.resolve(__dirname, 'src'),
extensions: [
'',
'.js',
'.json',
],
moduleDirectories: [
'src',
'node_modules',
],
},
};

Once we have that setup, we can add a build script to our package.json to build our client bundle.

// package.json
{
...
"scripts": {
...
"build": "webpack",
},
...
}

Then we can run it using npm run start in our terminal.


Writing your test runner configuration

Karma by default will look for a file called karma.conf.js to define our testing environment. In it we can configure which files are our test files, what browser to run it in and what plugins to use.

The two main plugins we’ll be using karma-jasmine and karma-webpack.

karma-jasmine provides the test environment with Jasmine’s functionality. This includes describe, it, expect and so on.

karma-webpack allows our test runner to compile all our Javascript— both in our tests and source code. For this to work we simply need to import our previously made configuration and modify it slightly.

// karma.conf.js
var webpackConfig = require('./webpack.config.js');
var config = function(config) {
return config.set({
basePath: '',
browsers: [
'PhantomJS',
],
frameworks: [
'jasmine',
],
files: [
'src/**/spec.js',
],
preprocessors: {
'src/**/*.js': [
'webpack',
'sourcemap',
],
'src/**/spec.js': [
'webpack',
'sourcemap',
],
},
    /**
* 1) We need to define that these variables are available globally.
*/
    webpack: Object.assign(
{},
webpackConfig,
{
externals: {
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true,
},
}
),
    /**
* 2) Even if you're using express, the webpackServer option is required to compile code through karma-webpack.
*/
    webpackServer: {
noInfo: true,
},
reporters: [
'progress',
],
colors: true,
autoWatch: true,
plugins: [
'karma-webpack',
'karma-jasmine',
'karma-sourcemap-loader',
'karma-phantomjs-launcher',
],
});
};
module.exports = config;

Awesome, now we should have a fully functioning test runner for React that uses Jasmine and Webpack. This would be a great time to watch a episode of Mr.Robot and get an Old Fashioned. jklol itsfast. lolololololol.


What Does It Do?

Before we jump into creating our little assertion environment, lets define what unit testing is meant to do. Simply put, the goal is to test the smallest parts in an application.

When writing unit tests, it’s good to get in the head space of “what are the minimum requirements for this thing to function as I want it to”. This will help you decide what assertions to write. When in the mindset of maintaining your codebase, you should expect your tests to tell you whether a recent change or module update broke what the component was supposed to do.

Making Some Assertions

Now for the tediously fun part, writing the specifications which our button will fulfill. The three things that we want to test are:

  • The Button component renders a button tag.
  • The Button component renders the label text we passed down to it.
  • The Button component should call the onClick prop that was passed to it if it exists.
import React from 'react';
import {shallow} from 'enzyme';
import Button from 'components/button';
describe('Button', () => {
let wrapper;
const props = {
onClick: jasmine.createSpy('onClick'),
label: 'Fire Photon Torpedos',
};
beforeEach(() => {
wrapper = shallow(<Button {...props} />);
});
it('should contain a `button` element', () => {
expect(wrapper.is('button')).toBe(true);
});
it('should contain the label passed to it', () => {
expect(wrapper.text()).toBe(props.label);
});
it('should call the `onClick` handler when the button is clicked', () => {
wrapper.simulate('click');
expect(props.onClick).toHaveBeenCalled();
});
});

What about DOM assertions like “should contain .someClassName"? I personally prefer not checking for such minute things unless its a product of a different case being rendered. For example in a authenticated component, rendering <LoggedIn /> or <LoggedOut /> depending on the current authentication status.


Running Your Tests

We’ll add two scripts to our package.json so we can easily execute either a single run or a continuous watcher for files changes in our project — either tests or source code.

// package.json
{
...
"scripts": {
...
"test": "karma start --single-run",
"test:watch": "karma start",
},
...
}

After we added those, lets run npm run test:watch!

☠️ Failure ☠️

Going Green

Now that we have some expectations that we can work towards, let’s make them green.

// src/components/button/index.js
import React from 'react';
export default ({onClick, label}) => (
<button onClick={onClick}>
{label}
</button>
);

It’s a blatantly dumb component, but it does what we expect to do!


Conclusion

Hopefully this article gave you a good overview of whats the point of unit testing is, what tools we can use to write and run them and how to actually do it.

The full repository with the example code is available here at https://github.com/aktof/example-react-unit-testing.