Enzyme: Invariant Violation: You should not use <Route> or withRouter() outside a <Router>

Created on 13 Sep 2017  路  43Comments  路  Source: enzymejs/enzyme

I'm trying to mount my Application component in a Jest/Enzyme test, but it's giving me an error:

Invariant Violation: You should not use <Route> or withRouter() outside a <Router>

According to react router 4 docs, the components are considered valid, where I can create components composed of <Route>s, then import them into another component and place inside a <Router>. Anyone else experience this or have a fix?

// versions
"jest": "^21.0.2",
"enzyme": "^2.9.1",
"react-router-dom": "^4.2.2",
"sinon": "^1.17.7",

Code if that matters:

My Application component

// Application.js
...
render() {
    return (
      <div>
        <Route path="/a" component={a} />
        <Route path="/b" component={b} />
        <Route path="/c" component={c} />
        <Route path="/d" component={d} />
      </div>
    );
  }
...

My app that's consuming the Application component

// app.js
...
render((
  <Provider store={store}>
    <I18nextProvider i18n={i18n}>
      <main>
        <Router history={browserHistory}>
          <Route component={Application} history={browserHistory} />
        </Router>
      </main>
    </I18nextProvider>
  </Provider>
), document.getElementById('app'));
...

My test case:

// application.spec.js
import React from 'react';
import { I18nextProvider } from 'react-i18next';
import sinon from 'sinon';
import { mount } from 'enzyme';
import Application from '../../../path/to/application';
import i18n from '../../../path/to/utils/i18n';

describe('<Application />', () => {

  const history = {
    push: jest.fn(),
  }

  it('calls componentDidMount', () => {
    sinon.spy(Application.prototype, 'componentDidMount');
    const wrapper = mount(<Application history={history} />);
    expect(Application.prototype.componentDidMount.calledOnce).to.equal(true);
  });

});

Most helpful comment

image

You can try <MemoryRouter>. I fixed a similar problem.

```diff
import React from 'react'
import { mount } from 'enzyme'
import { MemoryRouter } from 'react-router-dom'

const wrapper= mount(



  • )
    expect(wrapper).toMatchSnapshot()
    ```

All 43 comments

@mven take a look here #4795. it seems to be a expected behavior from RR4. I am exporting my component without withRouter and importing it in my test.

@jorgeitenjr Interesting -- do you have a code example?

@mven probably something like this:

export class MyComponent extends Component {
    handleSomeEvent(event) {
        this.props.history.push('/');
    }
}
export default withRouter(MyComponent)

Note that I export the higher order component as a default _while also_ exporting the class. I my app I use the default export (withRouter). And in my tests I do:

import { MyComponent } from './MyComponent';
// ^ not using default export here

test('it should call history.push', () => {
    const wrapper = mount(<MyComponent />);
    const push = jest.fn();
    wrapper.setProps({ history: { push } });
    // perform your test
});

Something like that

@LukasBombach
Duh...I didn't think of also "exporting the class". This works, thank you.

@LukasBombach would you recommend your approach for testing a component that has ? We are using Jest, and I'm getting this error. Warning: Failed context type: The context router is marked as required in Link,
but its value is undefined.

@dani-media I am not familiar with the issue but it appears to me that Link internally makes use of React's Context. You probably need to mock that context when mounting your component. Check this out https://reactjs.org/docs/context.html

Maybe this also helps: http://airbnb.io/enzyme/docs/api/ShallowWrapper/setContext.html

Did a bit of digging through react-router codebase to see where I could get the correct context objects so withRouter thinks it is wrapped by Router in tests... Using this to wrap all mount functions with context, seems to be working for me:

import { BrowserRouter } from 'react-router-dom';

// Instantiate router context
const router = {
  history: new BrowserRouter().history,
  route: {
    location: {},
    match: {},
  },
};

const createContext = () => ({
  context: { router },
  childContextTypes: { router: shape({}) },
});

export function mountWrap(node) {
  return mount(node, createContext());
}

createContext is a function because you can also use it to pass arguments to change context as necessary, eg if you are using react-intl and wish to test 'en' language in context, you can pass it in when you mount: mountWrap(node, lang) but it is not necessary 馃榾

@SteveGBanton Did you put that code in a separate file or do you include it on every test that is using mount? Could you post an example?

I am trying to test that my sidebar with links renders and by not using the withRouter default export I am now getting a different error that makes me think your fix will work.

TypeError: Cannot read property 'pathname' of undefined

@CWSites It could be in a separate file called, say contextWrapper.js in a test helpers directory. Here is an example describe block:

import React from 'react';
import { TableC } from '../../src/tablec';
import { mountWrap, shallowWrap } from '../testhelp/contextWrap';
import { expectedProps } from './mockdata'

describe('Table', () => {
  let props;
  let component;
  const wrappedShallow = () => shallowWrap(<TableC {...props} />);

  const wrappedMount = () => mountWrap(<TableC {...props} />);

  beforeEach(() => {
    props = {
      query: {
        data: groupData,
        refetch: jest.fn(),
      },
    };
    if (component) component.unmount();
  });

  test('should render with mock group data in snapshot', () => {
    const wrapper = wrappedShallow();
    expect(wrapper).toMatchSnapshot();
  });

  test('should call a DeepTable with correct props', () => {
    const wrapper = wrappedMount();
    expect(wrapper.find('DeepTable').props()).toEqual(expectedProps);
  });

});

Great, thank you @SteveGBanton!

8 hours ago. nice. i just have this problem now and solved it.

@SteveGBanton what is the shape() function that is being referred to here?

@abhinavkashyap92 must have stripped it out accidentally - it is just a PropType that should be imported at the top:

import { shape } from 'prop-types';

@SteveGBanton You're a lifesaver!

image

You can try <MemoryRouter>. I fixed a similar problem.

```diff
import React from 'react'
import { mount } from 'enzyme'
import { MemoryRouter } from 'react-router-dom'

const wrapper= mount(



  • )
    expect(wrapper).toMatchSnapshot()
    ```

@951565664 doesn't your solution using the MemoryRouter HOC cause you to have to update your snapshot every time you run your tests?

For me the key hash in the location object of the MemoryRouter keeps changing every time the tests are run. This causes me to have to update the snapshot too frequently.

See here is one run:

<Router
  history={
    Object {
      "action": "POP",
      "block": [Function block],
      "canGo": [Function canGo],
      "createHref": [Function createPath],
      "entries": Array [
        Object {
          "hash": "",
          "key": "i308n3",
          "pathname": "/",
          "search": "",
          "state": undefined
        }
      ],
      "go": [Function go],
      "goBack": [Function goBack],
      "goForward": [Function goForward],
      "index": 0,
      "length": 1,
      "listen": [Function listen],
      "location": Object {
        "hash": "",
        "key": "i308n3",
        "pathname": "/",
        "search": "",
        "state": undefined
      },
      "push": [Function push],
      "replace": [Function replace]
    }
  }
>

and then the next run:

<Router
  history={
    Object {
      "action": "POP",
      "block": [Function block],
      "canGo": [Function canGo],
      "createHref": [Function createPath],
      "entries": Array [
        Object {
          "hash": "",
          "key": "9xwjai",
          "pathname": "/",
          "search": "",
          "state": undefined
        }
      ],
      "go": [Function go],
      "goBack": [Function goBack],
      "goForward": [Function goForward],
      "index": 0,
      "length": 1,
      "listen": [Function listen],
      "location": Object {
        "hash": "",
        "key": "9xwjai",
        "pathname": "/",
        "search": "",
        "state": undefined
      },
      "push": [Function push],
      "replace": [Function replace]
    }
  }
>

notice the key attribute has changed on both the entries array and the location object.

Solved my issue by doing this:

@951565664 and others using memory router in snapshot tests. Keep in mind you're probably attempting to test the component not the component + the router, so if you snapshot your component you'll have more predictable snapshots. Here's an example:

  it('should render a <Button />', () => {
    const component = shallow(
      <MemoryRouter>
        <Button {...props} />
      </MemoryRouter>
    );
    expect(component.find(Button)).toMatchSnapshot();
  });
});

Can you try to use https://github.com/adriantoine/enzyme-to-json?

import toJson from 'enzyme-to-json'
 const tree = mount(
      <MemoryRouter>
        <Component />
      </MemoryRouter>
    )
 expect(toJson(tree, { noKey: true, mode: 'deep' })).toMatchSnapshot()

I don't update snapshot every time

In addition, I generally use the render() method.

 it('should render a <Button />', () => {
    const component = shallow(
      <MemoryRouter>
        <Button {...props} />
      </MemoryRouter>
    );
- expect(component.find(Button)).toMatchSnapshot();
+  expect(component.render()).toMatchSnapshot();
  });
});

I very much discourage that; use .debug() instead.

@SoccerGee you can do this: <MemoryRouter keyLength={0}>

@mven probably something like this:

export class MyComponent extends Component {
    handleSomeEvent(event) {
        this.props.history.push('/');
    }
}
export default withRouter(MyComponent)

Note that I export the higher order component as a default _while also_ exporting the class. I my app I use the default export (withRouter). And in my tests I do:

import { MyComponent } from './MyComponent';
// ^ not using default export here

test('it should call history.push', () => {
    const wrapper = mount(<MyComponent />);
    const push = jest.fn();
    wrapper.setProps({ history: { push } });
    // perform your test
});

Something like that

Followed this code syntax whenever I am JESTing using mount I get

Invariant Violation: You should not use <Route> or withRouter() outside a <Router>

And with shallow I get TypeError: Cannot read property 'submitHandler' of null

My testing is as follows

    const wrapper = shallow(<MyComponent />);
    const mockedEvent = { target: {}, preventDefault: () => {} };
    const spy = jest.spyOn(wrapper.instance(), 'submitHandler');
    wrapper.find('button').simulate('click', mockedEvent);
    expect(spy).toHaveBeenCalled();


    test('it should call history.push', () => {
     const wrapper = mount(<MyComponent />);
    const mockedEvent = { target: {}, preventDefault: () => {} };
    const spy = jest.spyOn(wrapper.instance(), 'submitHandler');
     // wrapper.setProps({ history: { push } });
     // perform your test
    wrapper.find('button').simulate('click', mockedEvent);
    expect(spy).toHaveBeenCalled();
 });

My submitHandler from my component contains

  submitHandler = e => {
    e.preventDefault();
    this.props.history.replace('', null);
    this.props.history.push({
      pathname: '/',
      state: {
        username: 'hello',
      },
    });
  };

Any insights? The code coverage fails on the props.history.push line and I cannot mock it via mount or shallow maybe because of

export default withRouter(MyComponent)

I am getting the same error but am not using withRouter, I am using RouteComponentProps on my component:

export class ConfigComponent extends React.Component<Props & RouteComponentProps, State> {

and get the error:

```
Error: Invariant failed: You should not use outside a
at Object. src/pages/Config.test.tsx:27

From console:
    Error: Uncaught [Error: Invariant failed: You should not use <Route> outside a <Router>]```

the components container is mounted inside a route:

<Route exact={true} path="/config" component={ConfigContainer} />

But I'm not importing the container in the route

Invariant Violation: You should not use <Route> or withRouter() outside a <Router>

same issue i have with testing what i have do.

Managed to get this working using

    const rendereC = mount(<Form.WrappedComponent />);
    const handleToUpdateMockedFn = jest.fn();
    rendereC.instance().validate = jest.fn().mockReturnValue(false);

Another question though is the code coverage cannot pass/ make assertions on variables and const inside class?

If I have

class User extends Component {

if ...
    variablename ...ternary ...
    .....other conditions

the Code coverage of Jest istanbul will never cover this if block of codes maybe because I need to refactor it to arrow functions or something...

According to https://stackoverflow.com/questions/48616566/jest-testing-variables-inside-component-methods "It is not possible to write assertions for the private internals of functions." but I would love other hacked solution or easier refactoring to handle this. thank you

thanks for keeping this thread open!

If you mock it, it won鈥檛 run, so it will correctly not be covered. Separately, private things should never be tested, but that鈥檚 a public instance method; and you never need arrow functions in class fields for anything.

Please file a new issue with more detail if you have a specific question.

Thank you for the response

if I have (for the sake of pseudocoding)

class myClass extends Component {
 ...
let firstname = '';
       if (CONDITION) {
      firstname =
        BLUE.firstname !== undefined ? PINK.firstname : '';
    }

return (
....
  {firstname}
....

So the code inside IF is not covered and others seem to bypass it with ignore istanbul comment and I don't want that so what are the proper assertions for that in order for the IF block to be covered 100% thanks

one test where CONDITION is truthy, and one where it's falsy. (This isn't unique to enzyme)

Thank you can you shed more technical insights for that assertion please?

  1. How do I mount that component IF block if it's not inside a function? It's within class but a code statement
  2. How do I call .toBeTruthy(thisblahblah) ... ????

It鈥檚 pretty hard to help without the full, unredacted code of your component.

import React, { Component } from 'react';

class Unredacted extends Component {

  render() {

    multiplyES6 = (x, y) => { return x * y };
    const prod = this.multiplyES6(4,6);

    let fname = '';
    if (prod ===24) {
    fname = prod !== 24 ? 'yahoo' : 'exciting';
  }

  return (
      <MyWrapper>
          <title>Login Page</title>
     <div>{fname}</div>
      </MyWrapper>        
    );
  }
}

export default Unredacted;

these lines are uncovered by Jest and I don't want to bypass it with ignore istanbul comment

if (prod ===24) {
fname = prod !== 24 ? 'yahoo' : 'exciting';
}

What is multiplyES6? You'll need a test where it returns 24, and another test where it doesn't.

However, do note that inside an "if prod is 24" block, prod !== 24 will always be false.

So my assertion would then be how? That is my specific question please? How do I mount do I shallow mount how do I call/assert the IF block part? Regardless of the pseudocode??

import React from 'react';
import { shallow } from 'enzyme';
import Unredacted from './Unredacted';
describe('Unredacted', () => {
  it('should blah blah what now', () => {
    const component = shallow(<Unredacted {...???} />);
    //  expect what to be truthy how do I assert properly?
    //  expect(ifBlockAssertionHow).toBeTruthy();

You don't. You just have to do anything such that your component renders in both of those two cases.

At this point we're wildly off topic for the issue; please file a new one, or ask on stack overflow, if you have further questions.

I still have this issue

@JoeQuattrone Try to change react-router-dom version to 4.3.1

@pdoongarwal Tried that and it didn't work.

Does anybody have a solution to this?

Usage of Router and Route from the react-router-dom solved this problem to me.

import { Router } from 'react-router-dom';
import { Route } from 'react-router-dom';

instead of:
import { Router } from 'react-router';
import { Route } from 'react-router-dom';

@alevshunov your solution worked for me! Was having issue with Prompt. Replaced react-router with react-router-dom. Thanks.

I had the same issue, i solved it with below code in index.js
Code with issue

ReactDOM.render(<App />, document.getElementById('root'));

Working code:

ReactDOM.render(
    <Router>
        <App />
    </Router>, 
    document.getElementById('root')
);

Had a similar issue, fixed by wrapping <App /> in <Router>.

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from "react-router-dom"
import App from './App';

it('Render test', () => {
  const div = document.createElement('div');
  ReactDOM.render(
    <Router>
      <App />
    </Router>, div);
  // tests
});

@luk82 you wouldn't generally use ReactDOM.render in an enzyme test - use mount instead

Was this page helpful?
0 / 5 - 0 ratings

Related issues

modemuser picture modemuser  路  3Comments

blainekasten picture blainekasten  路  3Comments

andrewhl picture andrewhl  路  3Comments

benadamstyles picture benadamstyles  路  3Comments

ahuth picture ahuth  路  3Comments