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

66,116

Solution 1

To test a component (with Jest) that contains <Route> and withRouter you need to import Router in you test, not in your component

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

and use it like this

app = shallow(
    <Router>
        <App />
    </Router>);

Solution 2

Utility Function To Wrap Mount With Context

Wrapping mount with Router in tests works, but there are situations where you don't want Router to be the parent component in your mount. Therefore I'm currently injecting context into mount using a wrapper function:

import { BrowserRouter } from 'react-router-dom';
import Enzyme, { shallow, mount } from 'enzyme';

import { shape } from 'prop-types';

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

export function shallowWrap(node) {
  return shallow(node, createContext());
}

This could be in a file called, say contextWrap.js, in a test helpers directory.

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: tableData,
        refetch: jest.fn(),
      },
    };
    if (component) component.unmount();
  });

  test('should render with mock 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);
  });

});

You can also use this pattern to wrap subcomponents in other types of context, for example if you are using react-intl, material-ui or your own context types.

Solution 3

You need to wrap the App with a BrowserRouter or an equivalent, see the below example of simple test case a component App that uses React Router

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

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(
<BrowserRouter>
  <App />
</BrowserRouter>,
div
  );
  ReactDOM.unmountComponentAtNode(div);
})

Solution 4

i had same problem, and first comment helped me, but there are a lot of code i have better way how to resolve this problem. See my solution below:

    import React from 'react';
import { shallow, mount } from 'enzyme';
import SisuSignIn from '../../../components/Sisu/SisuSignIn.js';
import { MemoryRouter } from 'react-router-dom';

const Container = SisuSignIn;

let wrapper;

beforeEach(() => {
  wrapper = shallow(<Container />);
});

describe('SisuSignIn', () => {
  it('renders correctly', () => {
    expect(wrapper).toMatchSnapshot();
  });
  it('should render one <h1>', () => {
    const wrapper = mount(
      <MemoryRouter>
        <SisuSignIn auth={{ loading: false }} />
      </MemoryRouter>
    );
    expect(wrapper.find('div').length).toBe(12);
  });
});

Solution 5

A keeps the history of your "URL" in memory (does not read or write to the address bar). Useful in tests and non-browser environments like React Native.

I got similar error solution is wrapping component with the help of Memory router

import { MemoryRouter } from 'react-router'

<MemoryRouter>
  <App/>
</MemoryRouter>
Share:
66,116

Related videos on Youtube

Maryja Piaredryj
Author by

Maryja Piaredryj

Software developer, dumb questions asker, stackoverflow lover =)

Updated on February 17, 2022

Comments

  • Maryja Piaredryj
    Maryja Piaredryj about 2 years

    I have a <UserListComponent /> which outputs one <Contact /> component and list of contacts presentated by <Contacts />.

    The problem is that in the test for <UserListComponent /> when I try to mount it, test outputs an error Invariant Violation: You should not use <Route> or withRouter() outside a <Router>

    withRouter() is used in <Contacts /> component.

    How can I mock ContactsComponent without router in test of parent component?

    I found some similar issue https://www.bountysource.com/issues/49297944-invariant-violation-you-should-not-use-route-or-withrouter-outside-a-router but it only describes situation when component is cover by withRouter() itself, not children.

    UserList.test.jsx

    const mockResp = {
      count: 2,
      items: [
        {
          _id: 1,
          name: 'User1',
          email: '[email protected]',
          phone: '+123456',
          online: false
        },
        {
          _id: 2,
          name: 'User2',
          email: '[email protected]',
          phone: '+789123',
          online: false
        },
        {
          _id: 3,
          name: 'User3',
          email: '[email protected]',
          phone: '+258369147',
          online: false
        }
      ],
      next: null
    }
    
    describe('UserList', () => {
      beforeEach(() => {
        fetch.resetMocks()
      });
    
      test('should output list of users', () => {
        fetch.mockResponseOnce(JSON.stringify(mockResp));
    
        const wrapper = mount(<UserListComponent user={mockResp.items[2]} />);
    
        expect(wrapper.find('.contact_small')).to.have.length(3);
      });
    
    })
    

    UserList.jsx

    export class UserListComponent extends PureComponent {
      render() {
        const { users, error } = this.state;
        return (
          <React.Fragment>
            <Contact
              userName={this.props.user.name}
              content={this.props.user.phone}
            />
            {error ? <p>{error.message}</p> : <Contacts type="contactList" user={this.props.user} contacts={users} />}
          </React.Fragment>
        );
      }
    }
    

    Contacts.jsx

    class ContactsComponent extends Component {
      constructor() {
        super();
        this.state = {
          error: null,
        };
      }
    
      render() {
        return (
          <React.Fragment>
            <SectionTitle title="Contacts" />
            <div className="contacts">
             //contacts
            </div>
          </React.Fragment>
        );
      }
    }
    
    export const Contacts = withRouter(ContactsComponent);
    
  • Mauricio Poveda
    Mauricio Poveda over 5 years
    Where is the 'mount' function coming from in the first file?
  • Steve Banton
    Steve Banton over 5 years
    It is enzyme's mount - I believe I'm setting it up globally with a jestSetup file before invoking tests that performs import Enzyme, { shallow, mount } from 'enzyme'; and sets global.mount = mount;. Added to answer for clarification, thanks very much.
  • Ben Lime
    Ben Lime over 5 years
    This should be the accepted answer. I think it is a lot cleaner then using the accepted answers approach.
  • Francis Rodrigues
    Francis Rodrigues over 5 years
    Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
  • Anupam Maurya
    Anupam Maurya over 5 years
    Invariant Violation: Could not find "store" in either the context or props of "Connect(BaseLayout)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(BaseLayout)".
  • Anupam Maurya
    Anupam Maurya over 5 years
    Invariant Violation: Could not find "store" in either the context or props of "Connect(Header)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Header)".
  • kaxi1993
    kaxi1993 about 5 years
    @AnupamMaurya use MemoryRouter instead of BrowserRouter and it will work.
  • worldsayshi
    worldsayshi almost 5 years
    @BenLime I agree, this solution allows using wrapper.setProps, wrapper.state, wrapper.setState with no further modification.
  • fgonzalez
    fgonzalez almost 5 years
    lifesaver. Thanks!
  • shorif2000
    shorif2000 over 4 years
    i am getting same error as @AnupamMaurya and i have used memory router
  • tamilmani
    tamilmani about 4 years
    @SteveBanton still am getting the same issue after using this can u pls help with this
  • tamilmani
    tamilmani about 4 years
    it's not working for me can anyone help me for this
  • Hiroki
    Hiroki over 3 years
    Does this really work with shallow? I think mount needs to be used
  • Moe Singh
    Moe Singh over 3 years
    This comment should be higher up as it works.
  • Gulam Hussain
    Gulam Hussain over 2 years
    Yes, for testing MemoryRouter should be preferred instead of BrowserRouter.