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 ( 39 ms )