redux: Can't reference containers wrapped in a Provider or by connect with Enzyme
I can’t seem to reference anything wrapped in a <Provider>
and a connect
// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null
let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node
// ContainerComponent
const Component = ({...}) => (<div id="abc"></div>);
export default connect(..., ...)(Component);
I followed: https://github.com/reactjs/redux/issues/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?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 7
- Comments: 51 (13 by maintainers)
Even though it’s not directly related to Redux. I solved this by calling
shallow()
on the container again. It renders the inner component with the store state passed into it.Example:
Hope this helps
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
connect()
actually does look forprops.store
before it looks forcontext.store
, so if you do actually feel like you need to test a connected component, you should be able to render<ConnectedComponent store={myTestStore} />
without needing to worry about Provider or context or anything.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
mapStateToProps
implementation, you can safely assume that react-redux will put them together correctly, and don’t need to actually test the connected version itself.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
component = shallowWithConnect()
After reading this thread several times, I reached the conclusion that there are 3 approaches recommended:
mapDispatchToProps
andmapStateToProps
and test them separatelymapStateToProps
and action creators instead ofmapDispatchToProps
and test those separately; write tests using the action creators, reducers and selectors together to make sure the whole flow works.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. I wrote in more detail about this on my blog, where I also have some code samples.
I plan on writing a HUGE blog post on this. I’m busy at the moment in code but will reply later
@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):
In other words, render your plain component, pass in spies for the props that would have been actions returned from
mapDispatch
, and trigger whatever behavior the component needs to make it call those actions.Also, per my earlier comment: you should be able to test your
mapStateToProps
andmapDispatchToProps
separately, and feel safe that React-Redux will call them appropriately, without having to try to test the connected version itself to verify that’s the case.@leizard
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.
@dschinkel :
The typical practice for defining Redux-connected components is to
export default connect()(MyComponent)
, and alsoexport MyComponent
as a named export. Similarly, becausemapState
is just a function, you can alsoexport const mapState = () => {}
, and then import and test it separately.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
connect
”. React-Redux already has a full test suite for how that should behave, and there’s no reason for you to be duplicating that effort. We know that it correctly handles subscribing to the store, callingmapState
andmapDispatch
, and passing props to the wrapped component.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
connect
wrapper, a test, or something else - it’s just how that component behaves given a certain set of props.(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
mapState
being accidentally broken, then write tests for it and move on.)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
const wrapper = mount(<Provider store={testStore}><MyConnectedComponent /></Provider>
. But, overall, my take is that most of the time you should be able to test yourmapState
function and the “plain” component separately, and you really shouldn’t be trying to test the connected version just to verify that the output ofmapState
is being passed to the plain component.For reference, you may want to look at the miniature version of
connect
that Dan wrote a while back to illustrate what it does internally: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e .Right, just saying that if you do use
mount
to check the lifecycle of the component in question, and that component renders other connected components, then you will need the store available in context for those nested components to avoid errors when the component being tested is rendered.(edit: I’m apparently sorta repeating myself, sorry - the hazards of answering questions in multiple places without always going back through a thread, or even scrolling up just a bit.)
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
<Provider />
into my tests and it seems like you’re testing a collaborator if you do that when the point isn’t to test that<Provider />
is working. You should test that the behavior in your component works and it doesn’t matter whether your container is getting the store via context. Just pass the store as a prop…connect just needs some way to get at the store, it doesn’t care how (context or props)…get rid of provider altogether in your tests.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.
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… In fact I’m going to share a repo soon that shows various styles and combinations and approaches to testing react-redux. I think it’ll help people because I am not the only ones thinking about this. Time and time again you see people asking the same questions around testing redux container and other components. The Redux docs nor do the react-redux docs do justice on that, they’re lacking documentation in the testing area IMO. It only scratches the surface, and we need a nice repo that shows various styles about approaching React testing so I’ll be putting that up soon.
If anyone would like to contribute to that repo, please get a hold of me
so I can add you ass a collaborator on it. I’d love to have people add examples. I’d like to see examples with straight up React Test Utils + mocha, with Enzyme, Ava, Jest, tape, etc.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 BTW for anyone interested, I started WeDoTDD.com last year (written in React btw). If a specific team (all devs on a particular team) TDD’s, or your entire company (some companies do have devs where all TDD especially consulting companies), please reach out to me on slack.wedotdd.com
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.
@philihp @dougbacelar I believe I grown a bit related to testing React applications. My current believes and thoughts about the subject:
mapStateToProps
andmapDispatchToProps
if you don’t need those mappings in multiple components. Exporting them just to test is an anti-pattern.=> The test setup for it actually pretty straightforward when you use the proper testing libraries (I recommend testdouble.js). => Mock out your selector and actions via testdouble.js (using
td.when()
) => Shallow render your container component,dive()
once to get access to the view component’s methods => Given the set of rules bytd.when()
assert if the component behaves correctlyNo need to inject some fakeStore library, it’s fine to stub out the store.
Example:
Thoughts?
@dschinkel
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:mapStateToProps
ormapDispatchToProps
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:
TestContainer.spec.jsx:
New Method
TestContainer.jsx:
TestContainer.spec.jsx:
Finding the right API/contract to test at and not to over test, that’s the key baby 😃.
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
mapStateToProps
andmapDispatchToProps
separately. However, there is one case these tests don’t catch, and that is whenmap(State/Dispatch)ToProps
don’t actually get passed into the call toconnect
. Example:This probably won’t ever happen in practice, but it could, especially since linters wouldn’t report
mapStateToProps
as an unused variable since it’s being exported.@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!
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
Ah. That helps me understand more.
So with the caveat that I actually have very little practical experience writing tests, a couple thoughts:
dispatch
. That’s not very easily testable in and of itself. You would probably want to be able to test their behavior as well, and to do that you’d want to define them as their own functions outside ofmapDispatch
.dispatch
.this.props.onSetDifficulty("HARD")
or something like that. When I suggested passing in spies for actions, this is the kind of thing I was suggesting replacing. So, if you passed in a spy foronSetDifficulty
, you could verify that your component called it, and passed in an acceptable value for the difficulty level.Ultimately, I think you should be able to make things a lot more testable by taking those functions defined in
mapDispatch
, and define them separately. Then you won’t have to worry about having to havemapDispatch
hooked up to test your component properly, and can focus on whether or not the component just called a given prop callback with the right arguments or something.Neither
connect()
norProvider
are part of this library. It is easier to discuss and track such issues when they are filed in the appropriate repository instead: https://github.com/reactjs/react-redux.