How to test High Order Components in React
Jaime Rios
Posted on August 13, 2018
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.
Posted on August 13, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.