react-router: Rendering component wrapped in `withRouter` throws when no Router is present

Version

4.0.0, 4.0.0-beta.8

Test Case

import React from 'react' // 15.4.2
import renderer from 'react-test-renderer' // 15.4.2
import { withRouter } from 'react-router'

const Component = () => <div />
const WrappedComponent = withRouter(Component)

it('will render', () => expect(renderer.create(<Component />)).toBeDefined())
it('will fail', () => renderer.create(<WrappedComponent />))

Steps to reproduce

Run above tests in Jest, or any other test runner.

Expected Behavior

Rendering WrappedComponent with react-test-renderer does not fail.

Actual Behavior

  ● will fail

    TypeError: Cannot read property 'route' of undefined

      at Route.computeMatch (node_modules/react-router/Route.js:66:22)
      at new Route (node_modules/react-router/Route.js:43:20)
      at node_modules/react-test-renderer/lib/ReactCompositeComponent.js:295:18
      at measureLifeCyclePerf (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:75:12)
      at ReactCompositeComponentWrapper._constructComponentWithoutOwner (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:294:16)
      at ReactCompositeComponentWrapper._constructComponent (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:280:21)
      at ReactCompositeComponentWrapper.mountComponent (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:188:21)
      at Object.mountComponent (node_modules/react-test-renderer/lib/ReactReconciler.js:46:35)
      at ReactCompositeComponentWrapper.performInitialMount (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:371:34)
      at ReactCompositeComponentWrapper.mountComponent (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:258:21)
      at Object.mountComponent (node_modules/react-test-renderer/lib/ReactReconciler.js:46:35)
      at ReactCompositeComponentWrapper.performInitialMount (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:371:34)
      at ReactCompositeComponentWrapper.mountComponent (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:258:21)
      at Object.mountComponent (node_modules/react-test-renderer/lib/ReactReconciler.js:46:35)
      at mountComponentIntoNode (node_modules/react-test-renderer/lib/ReactTestMount.js:55:31)
      at ReactTestReconcileTransaction.perform (node_modules/react-test-renderer/lib/Transaction.js:140:20)
      at batchedMountComponentIntoNode (node_modules/react-test-renderer/lib/ReactTestMount.js:69:27)
      at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-test-renderer/lib/Transaction.js:140:20)
      at Object.batchedUpdates (node_modules/react-test-renderer/lib/ReactDefaultBatchingStrategy.js:62:26)
      at Object.batchedUpdates (node_modules/react-test-renderer/lib/ReactUpdates.js:97:27)
      at Object.render [as create] (node_modules/react-test-renderer/lib/ReactTestMount.js:125:18)
      at Object.<anonymous> (src/components/Button/Button-snapshot.js:9:118)
      at process._tickCallback (internal/process/next_tick.js:109:7)

Route.computeMatch (node_modules/react-router/Route.js:66:22) is

  Route.prototype.computeMatch = function computeMatch(_ref, _ref2) {
    var computedMatch = _ref.computedMatch,
        location = _ref.location,
        path = _ref.path,
        strict = _ref.strict,
        exact = _ref.exact;
    var route = _ref2.route; // _ref2 is undefined

Passing { withRef: true } to withRouter has no effect.

This makes it hard to unit test any component that renders as one of its children a component wrapped in withRouter.

Can be worked-around by inserting a Router.

import { MemoryRouter as Router, withRouter } from 'react-router-dom' // 4.0.0

it('will pass', () => {
  expect(
    renderer.create(<Router><WrappedComponent /></Router>)
    .toJSON()
  ).toEqual({children: null, props: {}, type: 'div'})
})

Thanks!

possibly related bugfix here https://github.com/ReactTraining/react-router/issues/4292

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 17
  • Comments: 17 (8 by maintainers)

Most upvoted comments

if you’re not planning on rendering a component inside a <Router>, why are you wrapping it in withRouter

Because that’s how my application code uses it.

What’s preventing you from just not using withRouter when you unit test your component?

For shallow rendering a single component: Nothing, that’s what I’m doing now to work around this.

It requires me to export the unwrapped component and import that in my test, which is a mild inconvenience.

Imagine the case where you are, in a unit test, deep rendering your component under test, and it renders somewhere in its descendent tree a component that is wrapped in withRouter. How then should we just not use withRouter in that child?

Module-level mock the child to inject an unwrapped component? Gets messy quickly.

We ended up inserting a <Router> in our tests for those cases. This isn’t ideal because it breaks some enzyme functionality that can only operate on the root rendered element.

Crux of the issue is that I don’t expect withRouter to throw in this case, and think it would be nicer if it behaved as it did after https://github.com/ReactTraining/react-router/pull/4295 was applied to fix this issue once before.

Maybe I can phrase it differently: if you’re not planning on rendering a component inside a <Router>, why are you wrapping it in withRouter?

// This makes sense to me.
<Router>
  {withRouter(MyComponent)}
</Router>

// This doesn't.
<div>
  {withRouter(MyComponent)}
</div>

What’s preventing you from just not using withRouter when you unit test your component?

There are non-testing applications. Imagine a component that is used in two component trees – one having a Router and another not.

This actually happened in our codebase just moments ago

I’ll re-open for the time being for the ref issue, but I can say that I’m about 99% sure adding functionality to components just for testing purposes isn’t going to happen.

We ended up inserting a <Router> in our tests for those cases.

That’s the way all of our tests work here; they all render a <Router> at the top of the component hierarchy.

I get that this is really inconvenient for you, but I’m just not sure the best way to fix it. What does redux do if you render one of your connected components without a <Provider> up top? Does it just give you no props and a warning?

The testing guide covers the necessity of rendering inside of a <Router>.

I’m not sure if react-test-renderer allows you to pass in a context object, but if it does you can use react-router-test-context to simulate the context a router would provide. While I wrote it, I actually believe that it is usually better to just use a <MemoryRouter>. From my limited experience testing it, shallow rendering does not work well with <Route>s.

Also, I’m not positive, but is your reference to withRef just related to computeMatch having _ref and _ref2 as arguments? That is just a babel thing. I’m not actually sure how ref works with withRouter because I haven’t had a need to use them together. I know that some HOCs have to do some hoisting, but I haven’t seen anyone bringing up having issues with that yet.