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.
Top comments (6)
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 ?
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 thereact-boilerplate
repo, to see a more complete implementation of the same pattern.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.You are right, I've just edited the post and my source code.
Thanks for the feedback.
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
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:
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.