-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Can't reference containers wrapped in a Provider or by connect with Enzyme #1534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Neither |
I think that actually makes sense. Since you're shallow-rendering, only the Provider component will get rendered - your ContainerComponent will just be left in its object output form or whatever it is shallow rendering produces. Two thoughts here: First, note that the wrapper component generated by The second question is whether you really need to be worrying about actually testing the fully connected component. The argument I've seen made is that if you can test your "plain" component with specific props, and you can test your |
@gaearon you're right, sorry. Didn't know whether to raise this in react or enzyme repo. @markerikson Reason for testing smart component is for I will think about this more and raise an issue in the relevant repo if I need it. Thanks for the help. |
@Mordra : when you say "right dispatcher was called", do you actually mean "right action creator"? You could do something like this (syntax is probably off, but you should get the idea): let actionSpy = sinon.spy();
let wrapper = shallow(<PlainComponent someActionProp={actionSpy} />);
wrapper.find(".triggerActionButton").simulate("click");
expect(actionSpy.calledOnce).to.be.true;
expect(actionSpy.calledWith({type : ACTION_I_WAS_EXPECTING)).to.be.true; In other words, render your plain component, pass in spies for the props that would have been actions returned from Also, per my earlier comment: you should be able to test your |
@markerikson I can't do what you're suggesting because my const mapDispatchToProps = (dispatch) => {
return {
onCompleted : (player) => {
Meteor.call('newGame', player, function (data) {
console.log('new game return: ' + data);
});
dispatch(routeActions.push('/game'));
dispatch({type: "INIT_GAME", map: generateAreas(), buildings: generateBuildings(dispatch)});
},
onCancelled : () => dispatch(routeActions.push('/')),
onChangeName : input => dispatch(actions.changeName(input)),
onChangeGender : gender => dispatch(actions.setGender(gender)),
onSetDifficulty : lvl => dispatch(actions.setDifficulty(lvl)),
onChangeAttribute: (attr, val) => dispatch(actions.setAttribute(attr, val))
}
}; so if I spied on the props passed into the PlainComponent, I wouldn't be able to test whether the right actions are called, but rather, just the parameters passed to the action creators are correct. |
Ah. That helps me understand more. So with the caveat that I actually have very little practical experience writing tests, a couple thoughts:
Ultimately, I think you should be able to make things a lot more testable by taking those functions defined in |
Also, from the component's point of view: ultimately it's not actually worried about whether |
@Mordra in your case, I would export That said, I tend to eschew |
Finally note that you can use normal (not shallow) rendering. You would need either jsdom or a real browser for that, but then you can render any level deep.
Sorry, I did not mean React or Enzyme repos. I meant React Redux which is the library you are using. |
Thanks for the help guys, that's alot of info for me to go and digest. @markerikson @fshowalter I did some more looking around and will take your suggestion of exporting the mapState/Dispatch and testing them separately, it makes sense to test the callbacks that evoke the expected actions. @gaearon Stateless component doesn't render without shallow and just skimming the issues, there seems to problems rendering stateful components that has nested stateless components so I've made a mental note to avoid that path for now. |
I'm not sure which issues you are referring to. You can use shallow rendering with functional components just fine. Shallow rendering works only one level deep though (no matter how component is defined). This is its main feature—it doesn't recurse so component tests stay independent. If you have specific issues with shallow rendering that you can reproduce please file bugs against the React repo. Thanks! |
@gaearon : so to clarify, if I do this: let wrapper = shallow(<A><B /></A>); will B get rendered, or not? Because I think that's what the original question was - trying to render a |
Even though it's not directly related to Redux. I solved this by calling Example: import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';
const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
myReducer:{
someState: 'ABC'
}
};
let store;
let component;
describe('tests for MyContainerComponent', () => {
beforeEach(() => {
store = mockStore(storeStateMock);
component = shallow(<MyContainerComponent store={store} />).shallow();
});
it('renders container', () => {
expect(component.find('div.somediv')).to.have.length.of(1);
});
...
}); Hope this helps |
Should we make a more approachable way to do this through a custom module perhaps? Edit: So we can declare connect as one method for Enzyme, such as |
@ev1stensberg This is a great idea. Have we decided whether this is the approach and/or has work started on this? If not, I'd love to contribute. |
For anyone who stumbles upon this issue in the future, here's the approach that works for me: just test the plain component by itself. Export both the component definition as a named export and the connected component (for use in your app) as the default export. Going back to the code from the original question:
Then just do
And as others have pointed out above, if you also have coverage for the functions you pass into connect, you should have full coverage for your component as a whole. |
Well, the first thing to note here is the philosophy of the <Containers />:
If I'm ok then you only have to test 2 things:
So this is my approach import React from 'react';
import { shallow } from 'enzyme';
import { fromJS } from 'immutable';
import { createStore } from 'redux';
// this is the <Map /> container
import Map from '../index';
// this is an action handled by a reducer
import { mSetRegion } from '../reducer';
// this is an action handled by a saga
import { sNewChan } from '../sagas';
// this is the component wrapped by the <Map /> container
import MapComponent from '../../../components/Map';
const region = {
latitude: 20.1449858,
longitude: -16.8884463,
latitudeDelta: 0.0222,
longitudeDelta: 0.0321,
};
const otherRegion = {
latitude: 21.1449858,
longitude: -12.8884463,
latitudeDelta: 1.0222,
longitudeDelta: 2.0321,
};
const coordinate = {
latitude: 31.788,
longitude: -102.43,
};
const marker1 = {
coordinate,
};
const markers = [marker1];
const mapState = fromJS({
region,
markers,
});
const initialState = {
map: mapState,
};
// we are not testing the reducer, so it's ok to have a do-nothing reducer
const reducer = state => state;
// just a mock
const dispatch = jest.fn();
// this is how i mock the dispatch method
const store = {
...createStore(reducer, initialState),
dispatch,
};
const wrapper = shallow(
<Map store={store} />,
);
const component = wrapper.find(MapComponent);
describe('<Map />', () => {
it('should render', () => {
expect(wrapper).toBeTruthy();
});
it('should pass the region as prop', () => {
expect(component.props().region).toEqual(region);
});
it('should pass the markers as prop', () => {
expect(component.props().markers).toEqual(markers);
});
// a signal is an action wich will be handled by a saga
it('should dispatch an newChan signal', () => {
component.simulate('longPress', { nativeEvent: { coordinate } });
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0][0]).toEqual(sNewChan(coordinate));
});
// a message is an action wich will be handled by a reducer
it('should dispatch a setRegion message', () => {
component.simulate('regionChange', otherRegion);
expect(dispatch.mock.calls.length).toBe(2);
expect(dispatch.mock.calls[1][0]).toEqual(mSetRegion(otherRegion));
});
}); |
@markerikson what did you mean by:
lets say you're exporting the connected container via an ES6 export. You wouldn't have access to the private mapStateToProps. Were you talking about some other way or can you be specific? |
So if you expose your mapStateToProps and make it public, then say you render your ContainerComponent from your test including sending in say a fake store and sending in a state prop, then your mapStateToProps receives that state, maps it to a prop. But then how can you test it from that point? Connect will call mapStateToProps thus merging the props but where is the seam and which point is the seam in code during that process/flow where you can intercept the props being set on the presentational component? I guess double shallowing like @guatedude2 might be one way to check out the mapped props without any seams... also on your shallow.shallow. So the connect() component passes what back, a wrapper right? And that wrapper what wraps the presentational component? |
@granmoe can you explain this in more detail as to what you mean exactly:
|
I am also with @tugorez in terms of what I want to test and why. @markerikson so nice example with the spy. It's one thing you can test, but you'd need more assers there to test the "unit of behavior". Just testing that a spy action was called from mapDispatchToProps doesn't really tell us the whole story about the container component. It doesn't assert on the expected outcome based on your container's handler logic. It's not enough just to test that you've passed props or state, you want to test the behavior of the container component. That means testing two things:
|
The typical practice for defining Redux-connected components is to As for testing the "container component" vs the "presentational component": the general thought here is that you probably don't need to be concerned about testing the container for the most part, where "container" === "the output of What you as a consumer of the library are going to be interested in is how your component behaves. It shouldn't actually matter whether your own component is getting props from a (Also, FWIW, I realize you are absolutely the expert on testing and I am not, but it does seem like you're getting a wee bit paranoid about things :) If you're concerned about a Now, if your wrapped component then renders other connected components inside of it, and especially if you're doing full rendering instead of shallow rendering, then it may very well be simpler to test things by doing For reference, you may want to look at the miniature version of |
Yea, I mean if you export mapStateToProps and dispatchStateToProps, that would be better. I do not want to test that connect() (the collaborator in my test) works, definitely not. So that would be one clear way to do it, export those two methods: example-container-spec.js
Container
Presentation
Then again, you could also do something like this with shallow rendering and still dive into the child component: example-container-spec.js
Container
Presentation
yet another way to do this shallowly, is to do a double shallow. Shallow on the shallowed parent will shallow the child component that its wrapping, something like this:
So it's just another way, you're checking the outcome of your presentation component. Either way would work...however like you said mapStateToProps being exported is probably best, as that's all I care about testing in terms of setting prop state.
no I'm not, testing right meaning having good tests that are not brittle and are providing value is important. First off nobody is an expert. Everyone is learning continually, some just don't want to admit it due to their egos. It's called Software Craftsmanship (aka Professionalism). Testing only behavior and not testing collaborators (connect() itself) like you said is important. Figuring that out for the first time and testing well is important. Making sure you're not testing collaborators is important and that sometimes requires discussion amongst fellow devs. You can have brittle tests, and it's important not to have brittle tests. Testing should be taken seriously, and that means continual checking if you're approaching it right. I'm simply trying to figure out my flow. I TDD, so how I start out, is important, and good tests is important with that. So nobody should feel like they are "paranoid" when they seek to understand different ways to test React components. New React Test Repo Coming...
Conclusion: I think both approaches we've discussed are fine. Directly test the method, or test it like I did above, dive into it. Sometimes you don't want to base your tests on a specific method because you can get fragile tests...hence why people got stung in the past with testing. So whether or not to test around one function, whether that tests the "unit" depends. Sometimes you don't want to make a method public, and it might be better to test the contract above that and keep some of it private. So it's always important to think about what your'e doing often when you write tests. Nothing wrong with that. Writing good tests is not easy. That's exactly what TDD promotes, doing that often. So that you end up with leaner better less fragile code. TDD forces you to think about testing up front, not later. That's the difference and why people like me want to understand different flows and approaches because I have to when I TDD, it forces me to reassess design in little chunks often, and that means thinking about my testing approach often. @markerikson thanks for your inputs I love talking testing. Good stuff man. I wasn't questioning your expertise, just that I hadn't actually tried testing mapStateToProps directly myself! You're not doing bad for being a non-tester ;). I'm simply new to redux, simple as that so I will have questions that don't just "touch the surface". We should discuss testing at a lower-level so that people understand what they're doing, how they can do it, and if they're benefitting from their approach. To do that you have to understand connect, react-redux, provider at a lower level...so that you know how to "only test behavior". I haven't seen many people exporting mapStateToProps which is interesting to me also...and it makes me wonder why not. WeDoTDD.com |
Update. after playing more with testing connected components and reflecting on what's working great for me, I 100% agree now with @markerikson's statement here:
I haven't seen any need to introduce I also don't see a need to use enzyme's context option which is yet another way you could persist the store through React's context. Forget react's context altogether in your tests, it's totally unnecessary bloat and makes your tests more complex and much harder to maintain and read. Test your mapStateToProps pure function directly by exporting it in your container file. I'm using a combo of testing mapStateToProps + also a couple tests that test my container shallowly, verifying that the presentational component at least is receiving the props I expect and then I stop there with the props testing. I've also decided not to do any double shallowing for my container component tests. TDD is giving me feedback that by trying to do so, the tests get too complicated and you're probably doing something wrong. That "wrong" is "don't double shallow stuff for your container component tests". Instead create a new suite of presentational component tests that test the output of your presentational components independently. |
Glad to hear you've got things worked out. (FWIW, my comments were largely in response to your questions about "seams" and "digging into props", which really seemed like going to unnecessary levels of detail for little to no benefit.) Using a If you've got suggestions for improving Redux's current docs on testing, by all means file a PR. |
@markerikson good point on the mount. I'd only do a mount though if I needed to test the lifecycle of the component I'm testing. I wouldn't use it to deep dive into child components...that would no longer qualify as a unit test, and is basically integration testing and you'd be coupling that test to implementation details when you should test those child components on their own in isolation, not through a parent component. Anyway good stuff, thanks! |
ah ok got it! yea didn't think about that... |
Thanks for all the comments here, @markerikson and @dschinkel! They are very helpful. I've now decided to export my unconnected component and test it as I would any other, and also test my import React from 'react';
import { connect } from 'react-redux';
export const mapStateToProps = ({ name }) => ({ name });
export const Example = ({ name }) => <h1>{name}</h1>;
export default connect({ name } => ({ name: firstName }))(Example); // Whoops, didn't actually pass in `mapStateToProps`. This probably won't ever happen in practice, but it could, especially since linters wouldn't report |
@danny-andrews Could you be more specific about how to test your mapDispatchToProps function ? |
I don't test mapDispatchToProps or mapStateToProps directly anymore. I've changed my mind since this thread and advise not to do that. Also testing them you'll run into @danny-andrews problem but the issue is more about good tests and trying to expose those two functions is not a good idea. It's over testing. You should test those two methods indirectly by testing the behavior or by testing the props through your shallowed container instead. I found there is no reason to try to expose those private methods and I now realized that it was also a bad testing practice to try to do so. So I'm disagreeing with @markerikson, test through your connected component. |
Finding the right API/contract to test at and not to over test, that's the key baby :). |
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
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 MethodTestContainer.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 MethodTestContainer.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, { TestComponent } from './TestContainer';
it('renders TestComponent with approriate props from 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} />).find(TestComponent);
const actual = subject.prop('permissions');
expect(actual).toBe(userPermissions);
}); |
yea that's what I'm saying I don't export and test mapStateToProps, it's over testing. Test through the API. The API here is the Container. |
can you site some examples? |
I mean the same thing as you when you said: "You should test those two methods indirectly by testing the behavior" It might help to clarify terms: when I say "unconnected component," I mean the raw component which is wrapped in the call to // Unconnected component. Test all component logic by importing this guy.
export const TestComponent = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);
const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });
// Connected component (container). Only test interaction with redux by importing this guy.
export default connect(mapStateToProps)(TestComponent); Some people would prefer to split these entirely and have their "containers" just be calls to |
I plan on writing a HUGE blog post on this. I'm busy at the moment in code but will reply later |
After reading this thread several times, I reached the conclusion that there are 3 approaches recommended:
In the end, I guess all options are valid, but have their own pros and cons. The "purest" is probably the 2nd one, but also involves most work. My personal preference would actually go towards option 3. |
In connected components, export the base component (not connected), so that it can be tested without re-testing connect. This is possible, because in mapStateToProps, state is accessed via selectors, which can be tested separately. Also, in mapDispatchToProps, actions are defined by action creators that can also be tested separately. Some discussions on this matter: reduxjs/react-redux#325 (comment) reduxjs/redux#1534 https://jsramblings.com/2018/01/15/3-ways-to-test-mapStateToProps-and-mapDispatchToProps.html Ref #14
@ucorina That's why I'm leaning towards @dschinkel approach and not export mapDispatch and mapState, because to me they are private implementation of the connected component. However I'm not sure what to do with a component, which only wraps another component around a connect. Did you ever write the blog post? @dschinkel would love to read it |
@AnaRobynn Yeah, I agree with you that just exporting To answer your queston of "what to do with a component, which only wraps another component around a connect", I would say just don't test it. There's no business logic related to your application and you don't want to test the If you still want to test anyway, to future proof your code, you can always follow the 2nd approach I describe here which is closer to what @dschinkel is describing. |
@ucorina Exactly, I think option is the one I would go for as well! Thanks for explaining your answer more in depth. It's a nicely written blog post. Congratz! |
@ucorina Sorry to bother again, but after sleeping on it for a while I'm not sure I fully agree with option 2 either. I'm not sure though. I'm also not sure about the benefit of testing mapStateToProps at all. Do you think it's fine that "Option 2", indirectly also tests the action creators? And also indirectly testing selectors? Is that a good thing? |
Alternative:
|
@AnaRobynn I believe @ucorina's 2nd approach is the correct one. A lot of what I hear suggests:
Regarding 1, I don't think it's a good practice to expose internals in order to test. Your tests A: now assume internal structure, and B: should tests the thing how they're going to actually be used. Regarding 2, you absolutely should test something that calls an external library. This is one of the weak points in any application for breaking. With any library it's still possible for a breaking change to be introduced in a minor/patch version bump, and you want your tests to fail fast.
Yes, redundant test coverage is a good thing. |
@philihp @dougbacelar
=> The test setup for it actually pretty straightforward when you use the proper testing libraries (I recommend testdouble.js). No need to inject some fakeStore library, it's fine to stub out the store. Example: /** @format */
import React from 'react';
import { shallow } from 'enzyme';
function setup(props) {
const HasOrganization = require('./HasOrganization').default;
const defaultProps = {
store: {
subscribe: Function.prototype,
getState: Function.prototype,
dispatch: Function.prototype,
},
};
const container = shallow(<HasOrganization {...defaultProps} {...props} />);
return {
container,
wrapper: container.dive(),
};
}
describe('<HasOrganization /> collaborations', () => {
let getCurrentOrganizationId;
beforeEach(() => {
getCurrentOrganizationId = td.replace('../containers/App/userSelectors')
.selectCurrentOrganizationId;
});
test('not render anything when the id is not valid', () => {
const Dummy = <div />;
td.when(getCurrentOrganizationId()).thenReturn(null);
const { wrapper } = setup({ children: Dummy });
expect(wrapper.find('div').length).toBe(0);
});
test('render something when the id is valid', () => {
const Dummy = <div />;
td.when(getCurrentOrganizationId()).thenReturn(1);
const { wrapper } = setup({ children: Dummy });
expect(wrapper.find('div').length).toBe(1);
});
}); Thoughts? |
@AnaRobynn Strongly agree with points 1 and 2! My containers are usually very straight forward: // imports hidden
const mapStateToProps = (state, { userId }) => ({
isLoading: isLoading(state),
hasError: hasError(state),
placeholders: getPlaceholders(userId)(state), // a list of some sort
shouldDoStuff: shouldDoStuff(state),
});
const mapDispatchToProps = {
asyncActionPlaceholder,
};
export const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...dispatchProps,
...ownProps,
onMount: () => {
if (stateProps.shouldDoStuff) {
dispatchProps.asyncActionPlaceholder(ownProps.userId);
}
},
};
};
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
),
callOnMount(props => props.onMount())
)(SampleComponent); So I would
TL;DR I basically only test selectors were called with correct arguments, mocked actions were dispatched successfully(if they depend on any logic) and that the child component is rendered with the correct props
|
@dougbacelar Can you give some sample test code to test the component you showed. You are using enzyme? Something else? Shallow? Mount? |
Mock when you need to, but if you can use the real thing, why not? Mocking is useful when certain calls are slow, or have side effects like network requests.
Yes, test your selectors, action creators independently. Thoroughly test them with lots of expected inputs. Yes, also test your containers. If that means they provide overlapping coverage on selectors and action creators, that's not a bad thing. Here's what I mean... import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { saveColor } from '../actions/save';
import { selectColors } from '../reducers/colors.js';
import ColorButtons from '../components/ColorButtons';
const ColorButtons = ({ colors, onClick }) => (
<div>
{colors.map(color => {
<button type="button" key={color} onClick={onClick(color)}>{color}</button>
})}
</div>
);
ColorButtons.propTypes = {
colors: PropTypes.arrayOf(PropTypes.string),
onClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
colors: selectColors(state.colors),
});
const mapDispatchToProps = dispatch => ({
onClickColor: color => () => {
dispatch(saveColor({ color }));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ColorButtons); import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import ColorButtons from '../ColorButtons';
import { saveColor } from '../../actions/save';
import { selectColors } from '../../reducers/colors.js';
const buildStore = configureStore();
describe('<ColorButtons />', () => {
let store;
let wrapper;
const initialState = { colors: ['red', 'blue'] };
beforeEach(() => {
store = buildStore(initialState);
wrapper = shallow();
});
it('passes colors from state', () => {
expect(wrapper.props().colors).toEqual(selectColors(initialState.colors));
});
it('can click yellow', () => {
const color = 'yellow';
wrapper.props().onClick(color)();
expect(store.getActions()).toContainEqual(saveColor({ color }));
});
}); Here, this isn't testing all the different params that import { selectColors } from '../colors.js';
describe('selectColors', () => {
it('returns the colors', state => {
expect(selectColors(['red'])).toEqual(['red'])
})
it('returns an empty array if null', state => {
expect(selectColors(null)).toEqual([])
})
it('returns a flattened array', state => {
expect(selectColors(['red', ['blue', 'cyan']])).toEqual(['red','blue','cyan'])
})
}) |
Disagree, partly. In my collaboration tests I always mock modules, functions, etc
Also disagree. |
This thread is rehashing points made in https://martinfowler.com/articles/mocksArentStubs.html, Classical and Mockist Testing |
Agreed. Locking. |
I can't seem to reference anything wrapped in a
<Provider>
and aconnect
I followed: #1481 to the examples where the tests were written with enzyme, however, the containers are never tested in those. So I don't know whether I should/can test smart containers?
@fshowalter Any ideas?
The text was updated successfully, but these errors were encountered: