How to test High Order Components in React

Jaime Rios - Aug 13 '18 - - Dev Community

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);
}

Enter fullscreen mode Exit fullscreen mode

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:

  1. Create a basic store, with the property that our HOC needs to map to. In this scenario, is store.user.isLoaded.
  2. Creeate a component which the Auth should render when the user is authenticated.
  3. 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,
      },
    });
...
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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);
  });
});

Enter fullscreen mode Exit fullscreen mode

Conclusion

This test covers just one scenario, but all the assertions needed can start from here.

Cheers.

. . . . . . . . . . . . . . . . . . . . . . . .