DEV Community

Jaime Rios
Jaime Rios

Posted on • Edited on

How to test High Order Components in React

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.

Top comments (6)

Collapse
 
kepta profile image
Kushan Joshi • Edited

Interesting read, I have a few doubts:

I am not exactly sure why your proptypes include checks for render, componentWillMount, componentWillUpdate.

In the last test file, what is the Index file you are importing from ?

Collapse
 
papaponmx profile image
Jaime Rios • Edited

The reason why I include checks for render, componentWillMount, etc. Is just for consistency. Now that you have mentioned it, I'm not sure if they are necessary at all.

The import in the last test file, it should be Auth.js. In my pet project everything lives in a container folder. You can check out the react-boilerplate repo, to see a more complete implementation of the same pattern.

Collapse
 
kepta profile image
Kushan Joshi • Edited

Imho they aren't necessary and also do not make sense, since any reader like me would mistakingly read that this component expects props render, componentWillMount, which conflicts with React API.

Thread Thread
 
papaponmx profile image
Jaime Rios • Edited

You are right, I've just edited the post and my source code.

Thanks for the feedback.

Collapse
 
squishybear profile image
Wai Chung Hon

Hi Jaime,

Great post! My team is trying to write tests for the componentDidUpdate() method in our HOC, but we're getting stuck. Do you have any recommendations?

Thanks,

Wai

Collapse
 
papaponmx profile image
Jaime Rios

Hi, Wai. I'm glad to help.

Regarding your question, here is how I would go about it:

When using componentDidUpdate, I imagine it might happen because of a browser event, like an user clicking a button or focusing on an input.

At a component level, this might look like a method being triggered after being rendered.

This is what the code looks like:

describe('Test click button simulation', () => {
  it('should update the count by 1 when invoked by default', () => {
    const wrapper = shallow(<Home />);
    expect(wrapper.state('counter')).toBe(0);
    wrapper.find('button').simulate('click');
    expect(wrapper.state('counter')).toBe(1);
  });

Note that I'm component's state for this example, but you might use props if required. Here is a link with more examples.

Cheers.