For a tipical redux react application there are three levels at which it makes sense to test:

  • components
  • reducers
  • end-to-end (ui integration tests)

In this blog post I will cover component and reducer tests since end-to-end tests are normally written in an implementation agnostic way (e.g., selenium, webdriver) and are covered well by others already.

setting up environment

npm install react react-dom redux --save
npm install mocha expect expect-jsx \
babel-register babel-preset-es2015 \
babel-preset-react react-addons-test-utils --save-dev
echo '{ "presets": ["react", "es2015"] }' > .babelrc
src
├── components
│   ├── hello.jsx
│   └── hello.test.js
└── state
├── namesReducer.js
└── namesReducer.test.js

component

test properties & DOM

React renders virtual DOM therefore its easy to assert on properties or the DOM itself. expect-jsx is a handy expect extension that gives neat JSX diffs.

hello.test.js

import React from 'react';
import TestUtils from 'react-addons-test-utils';
import expect from 'expect';
import expectJSX from 'expect-jsx';
import Hello from './hello.jsx';

expect.extend(expectJSX);

const renderHello = name => {
const renderer = TestUtils.createRenderer();
renderer.render(<Hello name={name}/>);
return renderer.getRenderOutput();
}

const getChildrenByClass = (element, name) => {
return element.props.children.filter((value) => {
return value.props && value.props.class === name;
})[0];
}

describe('hello component', () => {
it('renders name', () => {
const output = renderHello('Steve');
const name = getChildrenByClass(output, 'test-name');
expect(name.props.children).toEqual('Steve');
});
it('renders component', () => {
const output = renderHello('Steve');
const expected = <div>Hello, <b class="test-name">Steve</b></div>;
expect(output).toEqualJSX(expected);
});
});

hello.jsx

import React from 'react';

const Hello = ({name}) => {
return (
<div>
Hello, <b class='test-name'>{name}</b>
</div>
);
};

export default Hello;

reducer

Since reducers are pure functions (have no side effects and only rely on parameter values) they are easy to test.

namesReducer.test.js

import expect from 'expect';
import namesReducer from './namesReducer';

const greet = name => {
return { type: 'GREET', payload: { name: name }};
};

describe('names reducer', () => {
it('greets', () => {
const state = namesReducer({ names: [] }, greet('Steve'));
expect(state).toEqual({ names: ['Steve']});
});
it('no greetings to Stranger', () => {
const state = namesReducer({ names: [] }, greet('Stranger'));
expect(state).toEqual({ names: []});
});
});

namesReducer.js

const shouldGreet = name => {
return name !== 'Stranger';
};

const namesReducer = (state = { names: [] }, action) => {
if (action.type === 'GREET' && shouldGreet(action.payload.name)) {
return { names: [...state.names, action.payload.name] };
}
return state;
};

export default namesReducer;

test results

 component
renders name
renders component

names reducer
greets
no greetings to Stranger

4 passing (39ms)