Skip to content

Instantly share code, notes, and snippets.

@danny-andrews
Last active August 18, 2017 17:50
Show Gist options
  • Save danny-andrews/118440f070c9903085e3eeab9a7cb895 to your computer and use it in GitHub Desktop.
Save danny-andrews/118440f070c9903085e3eeab9a7cb895 to your computer and use it in GitHub Desktop.
Redux Conventions

@dschinkel

You should test those two methods indirectly by testing the behavior... So I'm disagreeing with @markerikson, test through your connected component.

Are you suggesting that you should forego exporting the unconnected component entirely and only test the connected component? I think that is "over testing." This ties your tests unnecessarily to the implementation details of the component it is testing. Because the functionality of the unconnected component (if it has any, that's a whole other can of worms) is completely unrelated to where it receives its props from. Maybe you want to turn that component into a purely presentational component in the future. If you test the connected component, you'll have to change all your tests to not inject a store anymore but pass the props in directly. If you tested the unconnected component, you don't have to do anything except blow away the connected-component-specific tests (more on that below).

I do agree that you should not directly test mapStateToProps/mapDispatchToProps, though. Exposing those private methods simply for testing purposes always felt like a code smell to me. In fact, I think this is the only time you should test the connected component. So, to summarize:

  1. Export unconnected component and connected component
  2. DO NOT export mapStateToPropsor mapDispatchToProps
  3. Test all component logic via the unconnected component
  4. Only test connected component when testing interaction with redux (data props are passed in from the proper place in the store/action props are assigned to the proper action creators, etc.).

The only extra overhead with doing number 4 is that you have to stub out the redux store to pass it into your connected component. This is pretty easy, though, as it's API is just three methods.

MapStateToProps Method

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

export const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer); // Ewww, exposing private methods just for testing

TestContainer.spec.jsx:

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

import TestContainer, { mapStateToProps } from './TestContainer';

it('maps auth.userPermissions to permissions', () => {
  const userPermissions = {};

  const { permissions: actual } = mapStateToProps({
    auth: { userPermissions },
  });

  expect(actual).toBe(userPermissions);
});

New Method

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer);

TestContainer.spec.jsx:

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

import TestContainer from './TestContainer';

it('gets props from appropriate place in store', () => {
  const userPermissions = {};
  // Stubbing out store. Would normally use a helper method. Did it inline for simplicity.
  const store = {
    getState: () => ({
      auth: { userPermissions },
    }),
    dispatch: () => {},
    subscribe: () => {},
  };
  const subject = shallow(<TestContainer store={store} />);

  const actual = subject.prop('permissions');
  expect(actual).toBe(userPermissions);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment