Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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); 
like image 787
Maryja Piaredryj Avatar asked Apr 25 '18 15:04

Maryja Piaredryj


People also ask

Should not Use route or withRouter () outside a router?

To fix the 'You should not use Route or withRouter() outside a Router' error with React Router v4, we should wrap our app with the BrowserRouter component. import { BrowserRouter } from "react-router-dom"; ReactDOM. render( <BrowserRouter> <App /> </BrowserRouter>, document. getElementById("root") );

What is withRouter in react router?

withRouter is a higher order component that will pass closest route's match , current location , and history props to the wrapped component whenever it renders. simply it connects component to the router. Not all components, especially the shared components, will have the access to such router's props.

Is withRouter deprecated?

The library-provided HOC, withRouter, has been deprecated in React Router v6. If you need to use v6 and are using class-based React components, then you will need to write your own HOC which wraps the v6 use* hooks.

What is the difference between BrowserRouter and router?

The main difference between the two is the way they store the URL and communicate with your web server. A <BrowserRouter> uses regular URL paths.


2 Answers

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.

like image 32
Steve Banton Avatar answered Sep 28 '22 05:09

Steve Banton


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>); 
like image 125
Edhar Dowbak Avatar answered Sep 28 '22 04:09

Edhar Dowbak