Unit Testing React components that use Redux

Last Updated: August 1, 2016 | Created: July 5, 2016

React.js is build to make it easy to Unit Test, and there is plenty of information on how to do this. However as you build more complex applications and employ a store pattern like Redux then things get a little more complicated. This article goes through the issues you need to consider and is supported by a full set of open-source examples on GitHub.

This article is the forth in the series I am writing about creating a React.js samples that are a good starting point for real-world, production-ready applications with full React.js build, test, and deploy capabilities. The other articles in the series are:

  1. Templates for building React.js front-ends in ASP.NET Core and MVC5
  2. Using a Redux store in your React.js application
  3. Adding mocking to React.js Unit Tests
  4. This article: Unit Testing React components that use Redux

The aims of this article

  • Choosing the right React Testing Library to handle Redux.
  • How to test React components that uses Redux.
  • What to do about nested components that use Redux.
  • How to test events when Redux is involved.
  • Quick aside on other decorators like DragAndDrop.

I’m going to assume you at least know what React is and what Redux does. If you don’t know React try these article links [1, 2, 3]. I would also really recommend the book “Pro React”, which helped me a lot. If you don’t know what Redux is and why it is useful I cover that in my second article, Using a Redux store in your React.js application.

Note: One word of warning:  React.js is still changing and articles and books get out of date. This article and open-source AspNetReactSamples on GitHub uses React version 15.0.2  and Redux 3.5.2 (see package.json for full list).

Quick introduction

I had time between projects and I wanted to create a solid foundation for the development of React.js applications. In my first article I describe the tool chain that is needed to build and test React.js applications. As I am a full stack developer focused on Microsoft’s ASP.NET for the server side I build around that (Note: you don’t need any knowledge of ASP.NET to understand this article – its all about React.js).

In my first article I describe the Unit Test setup. So you don’t have to wade through the article here is a summary:

  • I chose Mocha as my test framework. Facebook recommend Jest for Unit Testing React, but a number of posts [1, 2, 3] said Jest was slow and Mocha was better. I have also used Mocha successfully in the past, which gave me confidence in this approach.
  • I used Karma as my test runner. Most people do and I have used it before.
  • I run my Unit Tests inside a real browser, Chrome, rather than a phantom browser so I can debug.
  • I used AirBnB’s Enzyme React testing utility. More on this later.
  • I added NPM scripts in the ReactWebPack.Core’s package.json file so that the tests could be run in same way as the build.

Choosing the React Testing Library

There are a number of unit testing libraries for React. The obvious one is React’s one ReactTestUtils. However I found ReactTestUtils didn’t have great documentation and it has some problems with Redux, e.g. ReactTestUtils doesn’t always seem to like the Redux’s Provider class, which is uses to pass in the Redux information to the React Components. Maybe there is a way to fix it, but I didn’t fine one.

Thankfully I found the excellent Enzyme React testing utility which is very well documented and also works with Redux. For the rest of this article I will use Enzyme.

Note: I do go through this in more detail later, but if you are an expert and want to just ‘look at the code’ to see how ReactTestUtils and Enzyme handle Redux connected components then look at the ConnectedClasses.test.js . I have commented on the ReactTestUtils tests that fail.

Testing a React components that uses Redux.

a: single, non-nested components

I created some very simple classes to check out the best way to test React components that use Redux. Let’s start with the simplest – InnerConnect. The content of this module is:

 
import React, {Component} from 'react';
import { connect } from 'react-redux';

export class InnerConnect extends Component {
    render () { return 
       <h2>
         Inner.dispatch {
           this.props.dispatch === undefined 
              ? 'undefined' : 'defined' }
      </h2>
    }
}
export default connect()(InnerConnect)

Note: For those not familiar with Redux the code above has the minimum it needs to get access to Redux. The connect()(InnerConnect) on the last line adds the property dispatch to the props. Normal use of Redux by a component would include more Redux code.

Redux’s documentation on Writing Tests suggests you export the React component class as well as the default, which should be the Class used (decorated by) in a connect() method.

Now let me give you a segment of the file ConnectedClasses.test.js which does a simple test with and without linking to Redux (or in this case redux-mock-store).

//... various includes but especially the one below
import InnerConnectConnected, {InnerConnect} 
    from '../../localSrc/InnerConnect';
describe('mount', () => {
    it('InnerConnect, no connect', () => {
        const wrapper = mount(<InnerConnect />);
        expect(wrapper.find('h2').length).toBe(1);
        expect(wrapper.find('h2').text())
           .toBe('Inner.dispatch undefined');
    });
    it('InnerConnectConnected, with connect', () => {
        const mockStore = configureStore([]);
        const store = mockStore({});
        const wrapper = mount(<Provider store={store}>
            <InnerConnectConnected />
        </Provider>);
        expect(wrapper.find('h2').length).toBe(1);
        expect(wrapper.find('h2').text())
            .toBe('Inner.dispatch defined');
    });
    //... more tests left out

The first test tests the InnerClass without the connect statement. The second test tests the default export, which contains the class decorated by the Redux connect method. In the tests I use enzyme’s mount which does a full DOM rendering. You can see the first test says this.props.dispatch is undefined while the second test says that this.props.dispatch is defined.

The important point is that if I had used the default export of InnerConnect (Referred to as InnerConnectConnected in the code above) and had NOT used <Provider> to supply a store then the test would have failed with the error:

Invariant Violation: Could not find “store” in either the context or props of “Connect(InnerConnect)”. Either wrap the root component in a <Provider>, or explicitly pass “store” as a prop to “Connect(InnerConnect)”.

Note: I could have used enzyme’s shallow render (there is an example of that inside ConnectedClasses.test.js), and that would of worked. However enzyme’s mount or render methods gives a more useful output in this case. I tend to use mount, but that is just a minor preference on my part.

b: Nested components

ReactKanbanNestedComponentsOK, that was pretty straightforward, but life in a real application is never as simple as that. React components are designed to be nested. In the Kanban applications, taken from the excellent book “Pro React”, uses a set of nested components to show multiple lists of multiple card, where each card may have multiple tasks (called CheckList in the diagram).

This is pretty normal for a React.js application. Also, if you use Redux then many of the React components will need to access Redux. Once this happens then things get more complicated.

Let us look at a second example React component called OuterConnect. The module is repeated in full below:

 
import React, {Component} from 'react';
import { connect } from 'react-redux';
import InnerConnect from './InnerConnect';

export class OuterConnect extends Component {
    render () { return 
       <div>
          <h1>
             Outer.dispatch {
                 this.props.dispatch === undefined 
                    ? 'undefined' : 'defined' }
          </h1>
          <InnerConnect />
       </div>
    }
}
export default connect()(OuterConnect)

You can see this is very similar to the InnerConnect, but it has one big difference – it has the InnerConnect component inside its render method – this is known as nesting. At this point the following happens:

  • Shallow render still works without the Redux <Provider> as long as you call the undecorated class, in this case InnerConnect.
  • Full Dom render will NOT work without the Redux <Provider>. This is because the nested class, i.e. InnerConnect, will fail because it uses the connect method which expects to find a Redux store.

As Full Dom rendering, i.e. enzyme’s mount method, often provides a more useful output then supplying the store via the Redux <Provider> class is the most sensible way to go.

Note: If you want a full list of all the test options then look at the test ConnectedClasses.test.js. The tests that fail are set to be skipped and have comments to say how they fail.

I have done a few tests on parts of the Kanban components. You can find them all in the directory Tests/components. I use a mixture of shallow and mount rendering.

Testing events when Redux is involved

React components need to respond to events, like clicking a button. If Redux isn’t involved then these events are normally handled by changing the this.state inside the component. However, once a Redux store, or any other sort of store pattern, is used the state is mainly (always?) handled by the store. This means that simulating an events is likely to trigger a Redux action.

You could provide a full implementation of the store and (I assume, because I haven’t tried it) that the action would ripple through and cause whatever change was expected in the html. However I personally use redux-mock-store so that I can decouple the actions. Here is a simple example taken from Card.test.js .

it('call close card event', () => {
const identity = function (el) { return el; };
    // Wrap in DnD test context
    const CardConnectedWithDnD = 
       wrapInTestContext(CardConnected);
    const mockStore = configureStore([]);
    const store = mockStore({});
    const card = createCard();
    const wrapper = mount(
        <Provider store={store}>
           <CardConnectedWithDnD 
               {...card} 
               connectDropTarget={identity}/>
        </Provider>);
    expect(store.getActions().length).toBe(0);
    wrapper.find('div .card__title').simulate('click')
    expect(store.getActions().length).toBe(1);
    expect(store.getActions()[0]).toEqual({ 
           payload: { cardId: 0 }, 
           type: 'toggle card details' });
});

As you can see simulating the click (line 16) causes an action to be called, which the redux-mock-store records. The test then checks that action has happened and has the right content.

Quick aside – other decorated components

While this article is mainly about components using Redux there are other types of components that use data passed down to them. The example I had to fix was the use of DragAndDrop but I expect there are others. These need a similar approach to how we handle Nested Redux usage. However the actual way you inject the data varies with the library you are using.

Do have a look at Card.test.js and List.test.js with the helper class WrapTestDnD.js .

Conclusion

I did have to think about the best approaches to testing nested React components that use Redux. Having built myself a useful set of example tests in the shape of the ConnectedClasses.test.js tests then I thought it was worth publishing a detailed run through of how things work.

I hope this helps others with their React.js developments. Please feel free to leave a comment if you have more information to add, or especially if you spot a error on my part.

Happy coding.