Testing Nested Container Components in React with Redux
Because I like LEGO and because I have some sort of wish to do things the inefficient way in my spare time, I’ve decided to build a LEGO management website similar to Rebrickable. Remarkably similar to Rebrickable. Like, doing much of the same stuff. In particular, I decided I didn’t want to upgrade to a Pro package for $5 a month and, instead, have spent dozens of hours learning React to build this website. Totally worth it, yes?
Any rate, I have spent a lot of time reading and trying to do things the right way; I’m using redux to deal with state, react-router to deal with navigation, flux standard actions for consistency, ducks for modularization and separation of concerns, jest for testing, and enzyme for better dom manipulations. I’m separating my components into display and container components since Dan Abramov said to. I know who Dan Abramov is. Hell, I even created my first NPM package (paging-dr-redux) to deal with pagination since none of the pagination packages seem to work.
And yet, something seemingly simple flummoxed me. I just couldn’t seem to make unit tests pass when I’m testing a component which embeds a container component, specifically one which is wired up using the connect
operation.
The first sign of trouble was when I ran the unit tests for a particular component; got an error which looked like this:
Invariant Violation: Could not find "store" in either the context or props of "Connect(PartListContainer)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(PartListContainer)"
PartListContainer
of course is a component wrapped by connect(mapStateToProps)(PartListContainer)
and being rendered by UserPartListPage
, a page which displays parts for a user. I wanted to use the common PartListContainer
because I also have a PartListPage
which is all the parts available; may as well make the loading, pagination, etc. in one component and just feed in the stuff which makes it global or user-specific.
Now, at this time I was using mount
in Enzyme, so I figured I could just use shallow
instead so it wouldn’t actually render the PartListContainer
. The issue here is that, suddenly, I didn’t have any children in my UserPartListPage
, and my “always render a part list” test started failing. I decided to try and render a Provider
wrapper in my tests, passing in an instance of redux-mock-store
but, that still didn’t give the right context to the PartListContainer
* and I went back to the “couldn’t find store” errors.
Frustrated, I decided that the problem must be in how I structured the components. However, I agree with the sentiment that we shouldn’t care about whether child components are containers or presentation-only; there are a lot of good reasons to include a component which only cares about managing its own state and doesn’t require its parent to handle that. Therefore, I started looking at ways to simplify my component tree while still allowing flexibility. At first, I subclassed PartListContainer
and had UserPartListPage
extend it. Then I remembered something else I read that, while it’s possible to extend, Facebook has never come across a real situation where inheritance is the best option; composition is basically always better. So, I unsubclassed and scratched my head.
By this point, had I been getting paid even minimum wage for the hours I’d put into trying to figure out this one problem, I could have bought the Pro plan for at least a year. I stepped back, took a day off.
And then I found the solution I like.
One of the problems I was having is that I felt the need to have a wrapper around the PartListContainer
(e.g. PartListPage
) because I was using those classes in my routes, like
<Route path='/parts' component={PartListPage} />
However, I discovered I could use the render
method render a component. Now, I’ve gone and done this:
<Route path='/parts'
render={
props => <PartListContainer paginator={paginator}
stateSelector={moduleSelector}
{...props} />
} />
And guess what? It works! Instead of needing a wrapper, I found a way to move passing the required params up a level. Now I don’t have a PartListPage
any more, so I don’t have to test it! I did the same thing (with different variables, of course) for the route which was using UserPartListPage
, removed that class and test, and everything renders correctly AND tests pass. Success!
Note: this does not actually resolve the problem of testing nested components. However, I’m still trying to find a solution because I truly believe that this should be a testable thing, and I’m frustrated that I haven’t found it yet. Maybe I’ll spend a few more years’ worth of Pro payments on the issue.
Footnotes
* While writing this, I believe I figured out why I wasn’t getting the context to the right place. In testing, I’ve been doing a named export of the class to be wrapped by
connect
and exporting/mounting that for my tests. If I wrap that with aProvider
, the store may not go through to its children. I, of course, have not tested this, given the fact that I went a different direction, but it may be something to try out if you get stuck like this.