How to test react-router with enzyme
Asked Answered
O

4

29

I am using enzyme+mocha+chai to test my react-redux project. Enzyme provides shallow to test component behavior. But I didn't find a way to test the router. I am using react-router as below:

<Router history={browserHistory}>
     ...
        <Route path="nurse/authorization" component{NurseAuthorization}/>
     ...
  </Route>

I want to test this route nurse/authorization refer to NurseAuthorization component. How to test it in reactjs project?

EDIT1

I am using react-router as the router framework.

Outgroup answered 8/1, 2017 at 10:0 Comment(0)
J
26

You can wrap your router inside a component in order to test it.

Routes.jsx

export default props => (
  <Router history={browserHistory}>
    ...
    <Route path="nurse/authorization" component{NurseAuthorization}/>
    ...
  </Route>
)

index.js

import Routes from './Routes.jsx';
...

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

Then you have to shallow render your Routes component, and you are able to create an object map to check the correspondance between path and related component.

Routes.test.js

import { shallow } from 'enzyme';
import { Route } from 'react-router';
import Routes from './Routes.jsx';
import NurseAuthorization from './NurseAuthorization.jsx';

it('renders correct routes', () => {
  const wrapper = shallow(<Routes />);
  const pathMap = wrapper.find(Route).reduce((pathMap, route) => {
    const routeProps = route.props();
    pathMap[routeProps.path] = routeProps.component;
    return pathMap;
  }, {});
  // { 'nurse/authorization' : NurseAuthorization, ... }

  expect(pathMap['nurse/authorization']).toBe(NurseAuthorization);
});

EDIT

In case you want to additionally handle the case of render props:

const pathMap = wrapper.find(Route).reduce((pathMap, route) => {
  const routeProps = route.props();
  if (routeProps.component) {
    pathMap[routeProps.path] = routeProps.component;
  } else if (routeProps.render) {
    pathMap[routeProps.path] = routeProps.render({}).type;
  }
  return pathMap;
}, {});

It will work only in case you render directly the component you want to test (without extra wrapper).

<Route path="nurse/authorization" render{() => <NurseAuthorization />}/>
Jukebox answered 8/1, 2017 at 15:11 Comment(16)
I followed your instruction but got below error on the line 'const wrapper = shallow(<Routes />);' - Object is not a constructor (evaluating 'Component(publicProps, publicContext, updateQueue)')Outgroup
BTW I am using 'react-router'Outgroup
@ZhaoYi Routes must be a React component (see Routes.jsx)Jukebox
This is great! It worked in App.js: import Routes from "./Routes" export default class App extends Component { render() { return ( <main> <Navbar/> <div className="page-body"> <Provider store={store}> <Routes/> </Provider> </div> </main> ); } } in index.js: import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; ReactDOM.render( <App />, document.getElementById('root') );Plea
Great answer, but be aware since it doesn't handle the case of using render instead of component directly: <Route path="..." render={props => <YourComponent {...props} />} />Herminiahermione
Yes, how do we check for the cases where we use render ?Sweyn
I think you cannot handle it automatically using the way I wrote. The render prop receive a function as value and call it injecting router props as argument.Jukebox
Awesome, am looking to do that actually. Could you please give an example for the same?Sweyn
I have the below code: const wrapper = shallow(<RouterOptions/>); const pathMap = wrapper.find(Route).reduce((pathMap, route) => { const routeProps = route.props(); pathMap[routeProps.path] = routeProps.render(routeProps, match); return pathMap; }, {}); jestExpect(pathMap['/some']).toBe(ContainerABC);Sweyn
I edited my answer with a way to handle both component and render props.Jukebox
Thank you much. It works for me, except I need to pass in the 'match' property to my render() method, along with router props. How do I pass these properties?Sweyn
You can inject a mock of match property pathMap[routeProps.path] = routeProps.render({ match: { params: {} } }).type;.Jukebox
Let us continue this discussion in chat.Sweyn
it's not worth the pain writing these kinds of tests. Don't test your routes to begin with. Just test lower-level behavior.Koral
@Koral I agree with you that is not the best way to unit test the routes, but I give a solution to the exposed issue. Changing testing strategy is not the solution to the actual problem, it's a way to avoid this problem to happen. You could write another answer with your advice because I think it deserve more visibility.Jukebox
the pathMap is not correct with nested routes, I guess it should iterate them in some sort of recurive mannerSleepy
L
2

I had my paths defined in another file for the dynamic router, so I am also testing that all the routes I am rendering as Routes are defined in my paths.js constants:

it('Routes should only have paths declared in src/routing/paths.js', () => {
  const isDeclaredInPaths = (element, index, array) => {
    return pathsDefined.indexOf(array[index]) >= 0;
  }
  expect(routesDefined.every(isDeclaredInPaths)).to.be.true;
});
Lacrimatory answered 25/4, 2017 at 9:47 Comment(0)
M
2

This will only pass if the component is rendered successfully: It works with Redux and react-router including hooks.

import React from "react";

import { expect } from "chai";
import { mount } from "enzyme";
import { MemoryRouter, Route } from "react-router-dom";
import { createMockStore } from "redux-test-utils";
import { Provider } from "react-redux";


...
describe("<MyComponent />", () => {
    it("renders the component", () => {
    let props = {
      index: 1,
      value: 1
    };
    let state = {};

    const wrapper = mount(
      <Provider store={createMockStore(state)}>
        <MemoryRouter initialEntries={["/s/parameter1"]}>
          <Route path="/s/:camera">
            <MyComponent {...props} />
          </Route>
        </MemoryRouter>
      </Provider>
    );

    expect(wrapper.find(ProcessedFrames.WrappedComponent)).to.have.lengthOf(1);
  });
});
Machinery answered 5/6, 2020 at 15:6 Comment(0)
S
0

Tested for react-router-dom v6

Based on @Freez 's answer, I implemented a recursive function that returns a correct url map even if you are using nested routes.

You just need to add this once in setupTests.js for jest tests to be able to use it in any test:

function recursiveGetPathMap(route, parentPath){
  let pathMap = {};
  const routeProps = route.props();
  let path = parentPath + (parentPath.length == 0 || parentPath[parentPath.length-1] == '/' ? '' : '/') + routeProps.path;
  pathMap[path] = routeProps.element.type;
  route.children(Route).forEach(el=>{
    pathMap = {...pathMap, ...recursiveGetPathMap(el, path)};
  });
  return pathMap;
}

global.getPathMap = (wrapper)=>{

  let pathMap = {};
  wrapper.find(Routes).children(Route).forEach(el =>{
    pathMap = {...pathMap, ...recursiveGetPathMap(el, "")};
  });

  return pathMap;
}

Example:

App.js

...
<Routes>
    <Route path="/" element={<Layout/>}>
        <Route path="users" element={<Users/>}>
            <Route path=":name" element={<Profile/>}/>
        </Route>
    </Route>
</Routes>
...

App.test.js

...
it('whatever', ()=>{
    const component = <App/>;
    const wrapper = shallow(component);
    const pathMap = getPathMap(wrapper);
    expect(pathMap['/']).toBe(Layout);
    expect(pathMap['/users']).toBe(Users);
    expect(pathMap['/users/:name']).toBe(Profile);
});
...

The output of console.log(pathMap) in that example is:

{
    '/': [Function: Layout],
    '/users': [Function: Users],
    '/users/:name': [Function: Profile]
}

Note that if you have a route without path (index route):

<Route index element={<SomeComponent/>}/>

the route will be like /somepath/somepath/undefined

Sleepy answered 25/5, 2022 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.