Step 22

Testing Connected Components

Testing components that are connected with Redux is a little tricker as you cannot simply mount the component with enzyme until you have mocked the store, otherwise errors will occur. In order to make this process simple we can use an open source packages called redux-mock-store.

Let's get started by installing this to our development dependencies.

yarn add --dev redux-mock-store

No configuration is necessary so we can jump right back into the Map spec and begin creating a mock store.

We will add a new top-level describe context to the spec which will concern the connected version of the Map. This means we will also have to import the ConnectedMap component.

// app/js/components/Map/__specs__/Map.spec.jsx

  import React from 'react';
  import { shallow } from 'enzyme';

  import { pointsMock } from '../../../spec-helper';
- import { Map } from '../';
+ import ConnectedMap, { Map } from '../';

  describe('Map component', () => {
    describe('when there are no points', () => {
      it('matches the snapshot', () => {
        const wrapper = shallow(<Map points={[]} />);

        expect(wrapper).toMatchSnapshot();
      });
    });

    describe('when there are points', () => {
      it('matches the snapshot', () => {
        const wrapper = shallow(<Map points={pointsMock} />);

        expect(wrapper).toMatchSnapshot();
      });
    });
  });
+
+ describe('ConnectedMap component', () => {});

We can now import the configureStore function from redux-mock-store. We call this function and pass it Redux a list of middleware libraries that our application uses. Since we do not have any custom middleware in this application we will pass an empty array to configureStore. This function returns a new function which we will name mockStore that allows us to build a mocked Redux store. We can pass any data we want to the mockStore function and it will be used as the store state. The returned value from mockStore is the Redux store object which we must give to the connected component we want to test as a prop.

We will pass the pointsMock mock data as our store value. We must however pass an object { points: pointsMock } because we are telling the mock store that the pointsMock array is the state of the points reducer. Basically, the keys of the object you pass must match the reducer names that were defined in your combineReducers call inside of app/js/reducers/index.js.

The final step, as with the unconnected components, is to mount the component using shallow and tell Jest to expect the mounted component matches the snapshot.

// app/js/components/Map/__specs__/Map.spec.jsx

  import React from 'react';
  import { shallow } from 'enzyme';
+ import configureStore from 'redux-mock-store';

  describe('ConnectedMap component', () => {
+   const mockStore = configureStore([]);
+   const store = mockStore({ points: pointsMock });
+
+   it('maps store state to the props', () => {
+     const wrapper = shallow(<ConnectedMap store={store} />);
+
+     expect(wrapper).toMatchSnapshot();
+   });
  });

If you run the tests with yarn test in your terminal you should see all tests passing and a new snapshot for the connected component has been written.

Take a look at the app/js/components/Map/__specs__/__snapshots__/Map.spec.jsx.snap file and you will see the following snapshot has been written.

exports[`ConnectedMap component maps store state to the props 1`] = `
<Map
  dispatch={[Function]}
  points={
    Array [
      Object {
        "details": Object {
          "house": "Night's Watch",
          "name": "The Wall",
          "words": "Night gathers, and now my watch begins.",
        },
        "favourite": true,
        "id": 1,
        "x": 450,
        "y": 110,
      },
      Object {
        "details": Object {
          "house": "Stark",
          "name": "Winterfell",
          "words": "Winter is Coming",
        },
        "favourite": false,
        "id": 2,
        "x": 375,
        "y": 355,
      },
    ]
  }
  store={
    Object {
      "clearActions": [Function],
      "dispatch": [Function],
      "getActions": [Function],
      "getState": [Function],
      "replaceReducer": [Function],
      "subscribe": [Function],
    }
  }
  storeSubscription={
    Subscription {
      "listeners": Object {
        "clear": [Function],
        "get": [Function],
        "notify": [Function],
        "subscribe": [Function],
      },
      "onStateChange": [Function],
      "parentSub": undefined,
      "store": Object {
        "clearActions": [Function],
        "dispatch": [Function],
        "getActions": [Function],
        "getState": [Function],
        "replaceReducer": [Function],
        "subscribe": [Function],
      },
      "unsubscribe": [Function],
    }
  }
/>
`;

Although we don't see the actual Pointer instances that the Map would generate with this data as we can see in the unconnected snapshots, we do see what information Redux is passing to the Map. The store, storeSubscription, and dispatch props can be ignored as they are internals of react-redux but we do see the points prop that Redux will automatically pass into the Map component is the mock data that we expected from the store. This tells us the mapStateToProps function from the Map is working correctly.

We now know how to test connected and unconnected components. We will continue in the next steps to cover the rest of our components, starting with the App.