testing redux react applications
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)
If you liked this post, you can
share it with your followers
or follow me on Twitter!