enzyme: Invariant Violation: You should not use or withRouter() outside a

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);
  });

});

About this issue

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

Most upvoted comments

image

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

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

const wrapper= mount(
+ <MemoryRouter>
        <Link to='/a' />
+  </MemoryRouter>
)
expect(wrapper).toMatchSnapshot()

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 😀

@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

@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);
  });

});

@LukasBombach would you recommend your approach for testing a component that has <Link to={“/somepath”}>Link</Link>? 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.

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

import { shape } from 'prop-types';

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();
  });
});

@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.

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

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’;

@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

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