@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:
- Export unconnected component and connected component
- DO NOT export
mapStateToProps
ormapDispatchToProps
- Test all component logic via the unconnected component
- 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.
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);
});
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);
});