Adding a Link to a React component was easy as it sounds, except that it broke half of the unit tests. I started getting “Invariant Violation: You should not use <Link> outside a <Router>” which explains itself, but sorting it out took quite some time. In my component I’ve been using withRouter, but the tests are done on a disconnected component without the router and the Link component requires the router context. While mostly understood the issue, when trying most of the suggestions, I kept getting the above error, or “Warning: Failed context type: The context router is marked as required in Component, but its value is undefined“. So let’s see how to pass the router context to child components when testing in Enzyme/Jest.

Create the environment

To understand the problem, let’s say I’m about to create a NavigationBar component which includes a Link or NavLink. The component itself is a child component of some other (in this case my App) component and somewhere some parent wraps it in a BrowserRouter. So the application itself works nicely, my NavigationBar component gets the router context and passes it to the Link. But when the NavigationBar is unit tested with Jest/Enzyme, it’s a standalone component and isn’t wrapped into a Router.

For simplicity I stripped down an application created using create-react-app and created a component to illustrate a possible issue. Now it looks like this:

The wannabe unit test

Now, I figure this example might not be the best, but it shows, that as soon as the test is trying to render the Link, it will throw some of the above errors.

Disclaimer: I know I probably should not test this. At this point I’m actually testing if the Link component is rendering correctly its children, whereas I should check if it has the expected children. I’m leaving it like this anyway because I’m not good at coming up with simple examples and let’s say this is not a unit, but an integration test.

Wrap it in a Router

The first (bad) advice I found was to wrap the tested component in some Router, for example a MemoryRouter. Now this kind of solves the problem of the missing Router component and can make some parts work, but it will become a problem if you want to access the props or state of the tested component. Let’s say I want to call setProps in the above example to see if the title changes as I change the title prop. As soon as it’s wrapped in the MemoryRouter, I can’t do it. From the Enzyme wrapper I can only access the props of the root component. So now I want to do it, but this just will not work:

Set up a router context

When creating the Enzyme wrapper, either shallow or full DOM rendering, options can be passed as a second parameter, which may include a context. It depends on the component which requires the router how complex the router context should be. This one seems to fulfill all my needs now (and actually more, but leaving it here anyways). This is the options object which I will pass to the wrapper:

So the wrapper setup now looks like this:

After this came a half day suffering of trying to make it actually work in different permutations. Whatever I’ve added to the context, the Link kept saying that the router context is undefined and I should not use it outside a Router. Now, what I didn’t realize for a while was, that although I’m using shallow rendering, but then I rendered a child component. And shallow() is not really cut out for that. When wrapping my component into a ShallowWrapper, the child component (in this case Link) did not get the context, whatever I did. Makes sense, but had to realize it.

Mount instead of shallow

Not having a better idea, let’s make a full DOM rendering then. At this point I hated my life a bit more, because after passing the above context (which I was fairly confident about) and replacing shallow() with mount() it just started throwing “Warning: Failed context type: The context router is marked as required in Link, but its value is undefined.”.

childContextTypes

The missing piece of puzzle, which I’ve seen before, but didn’t seem to do any good was another wrapper option childContextTypes. I’ve tried to set it before, because in some examples it was used with shallow(), but actually shallow doesn’t have this option (should have read the documentation instead of forums). childContextTypes is an object which describes all contextTypes passed for all children. Without setting this, even mount will not pass the context down to the children, but at least mount does have this option.

To describe the context types I’ve imported shape from prop-types for now and created a definition like:

And this finally did the trick. I have a context which I can monitor for changes if needed, don’t have to wrap my tested component and the children got the context they needed.

Complete test

Not complete in the sense of implemented tests, but now it is finally possible to test what I wanted without breaking other tests.

Of course now it’s better to put this into a separate module which is only responsible for generating the context, but this way it’s easier to overview the whole test unit.

Source code

The complete project is available in my repo: https://github.com/seabadger-io/router-context




Tagged with