Intro
Note: I'm assuming you are somewhat familiar to unit testing in JavaScript and know what a High Order Component is.
I'm adding unit tests to one of my pet Projects. I'm using react-boilerplate
as a starter app, so Enzyme and Jest are already plugged in.
This is a brief walk trough for a problem I just encountered.
The problem
Testing HOCs, is a quite particular scenario, since it is uses mapStateToProps
and a High Order component, of course.
Let's take your classic Authentication
component. It reads a boolean
from state, and evaluates whether the user is authenticated or not, and returns the component or redirects to a given URL accordingly.
This is what our component looks like:
/**
*
* Auth.js
*
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
export default function(ComposedComponent) {
class Authentication extends Component {
/* eslint-disable */
componentWillMount() {
if (this.props.authenticated === false) {
return this.props.changePage();
}
}
componentWillUpdate(nextProps) {
if (nextProps.authenticated === false) {
return this.props.changePage();
}
}
/* eslint-enable */
render() {
return <ComposedComponent {...this.props} />;
}
}
Authentication.propTypes = {
authenticated: PropTypes.bool,
changePage: PropTypes.func,
};
const mapStateToProps = state => ({ authenticated: state.user.isLoaded });
const mapDispatchToProps = dispatch =>
bindActionCreators({ changePage: () => push('/login') }, dispatch);
return connect(
mapStateToProps,
mapDispatchToProps,
)(Authentication);
}
The solution
For the store, we well use a sweet library that allows us to mock a Redux store, as you can imagine, it is called redux-mock-store
.
yarn add redux-mock-store --dev
Then our test should do the following:
- Create a basic store, with the property that our HOC needs to map to. In this scenario, is
store.user.isLoaded
. - Creeate a component which the
Auth
should render when the user is authenticated. - Assert that it returns(or renders) something.
Lets go step by step.
import configureStore from 'redux-mock-store';
import Auth from './Auth';
let store;
describe('<Auth /> test', () => {
beforeEach(() => {
const mockStore = configureStore();
// creates the store with any initial state or middleware needed
store = mockStore({
user: {
isLoaded: true,
},
});
...
Notice that mockStore is taking as an argument, what our state should look like. Since Auth
only cares about user.isLoaded
, we'll set it as true and evaluate that Component
is being rendered. We'll use the most generic I can think of at this time.
// Auth.spec.js
...
it('Should render the component only when auth prop is true', () => {
const Component = <h1>Hola</h1>;
const ConditionalComponent = Auth(Component);
const wrapper = shallow(<ConditionalComponent store={store} />);
expect(wrapper).not.toBe(null);
We just need to pass as a prop the store we just created, and then assert that our component is being rendered.
Our test in one single file.
/**
*
* Auth.spec.js
*
*/
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import Auth from '../Auth';
let store;
describe('<Auth />', () => {
beforeEach(() => {
const mockStore = configureStore();
// creates the store with any initial state or middleware needed
store = mockStore({
user: {
isLoaded: true,
},
});
});
it('Should render the component only when auth prop is true', () => {
const Component = <h1>Hola</h1>;
const ConditionalHOC = Auth(Component);
const wrapper = shallow(<ConditionalHOC store={store} />);
expect(wrapper).not.toBe(null);
});
});
Conclusion
This test covers just one scenario, but all the assertions needed can start from here.
Cheers.