10

I have followed the examples closely but I cannot get the MemoryRouter (is this how you are supposed to test route components?) to work with a test using jest and enzyme.

I would like to navigate to one of the routes, and have that reflected in my snapshot. The code below attempts to navigate using MemoryRouter to "/A" so I assume I would see <div>A</div>

import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';

Enzyme.configure({adapter: new Adapter()});

describe('Routing test', () => {
    let wrapper;

    beforeEach(() => {
        wrapper = mount(
            <MemoryRouter initialEntries={["/A"]}>
                    <div className={"Test"}>This is my Test Component and should not have any test specific code in it
                        <Router>
                            <Switch>
                                <Route path={"/A"}>
                                    <div className={"A"}>A</div>
                                </Route>
                                <Route path={"/B"}>
                                    <div>B</div>
                                </Route>
                            </Switch>
                        </Router>
                    </div>
                </MemoryRouter>
        );
    });
    afterEach(() => {
        wrapper.unmount();
    });

    it('matches snapshot', () => {
        expect(wrapper.find(".Test")).toHaveLength(1); //this ok
        expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find  A
    });
});

Instead of seeing <div>Test<div>A</div></div> I just see <div>Test</div>

NOTE: My example is simplified into one class. My real world situation is that <div>Test...</div> is a seperate component.

Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225

4 Answers4

4

OK I figured it out.

Its very ugly but you need to create a __mocks__ directory (In the first level of your project). __mocks__ seems to be poorly documented but it seems to be a jest thing, and everything in here will be run when testing, and here you can add mock stubs for certain external libraries.

import React from 'react';

const reactRouterDom = require("react-router-dom")
reactRouterDom.BrowserRouter = ({children}) => <div>{children}</div>

module.exports = reactRouterDom

My test file is the same as in my question (i think) :

import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';

Enzyme.configure({adapter: new Adapter()});

describe('Routing test', () => {
    let wrapper;

    beforeEach(() => {
        wrapper = mount(
            <MemoryRouter initialEntries={['/A']}>
                    <div className={"Test"}>This is my Test Component and should not have any test specific code in it
                        <Router>
                            <Switch>
                                <Route path={"/A"}>
                                    <div className={"A"}>A</div>
                                </Route>
                                <Route path={"/B"}>
                                    <div>B</div>
                                </Route>
                            </Switch>
                        </Router>
                    </div>
                </MemoryRouter>
        );
    });
    afterEach(() => {
        wrapper.unmount();
    });

    it('matches snapshot', () => {
        expect(wrapper.find(".Test")).toHaveLength(1); //this ok
        expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find  A
    });
});

This works and my test is green! :)

UPDATE :

I think I got a bit confused because I was treating the Router like any other react component, when it actually is a top level component like redux Provider. Router should not be inside the App but outside the App like so (in an index.js file for example).

ReactDOM.render(
    <Provider store={store}>
        <Router>
            <App/>,
        </Router>
    </Provider>,
    document.getElementById('root')
);

Now when writing tests against App, I provide my own router such as MemoryRouter.

Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225
3
  1. I can't find any proof of this but I always was under impression than you should use only one <Router> somewhere at the top of the tree and shouldn't nest them.
    So I've looked in the source code myself, and if I got it right, this is true. Because:

    1. react-router uses Context API to pass props down the hierarchy.
    2. From React docs:

      [...] it will read the current context value from the closest matching Provider above it in the tree.

    3. <Router> is a Provider but not a Consumer, so it can't peek up props from a parent <Router>
  2. When people advocate for tests they also mention that writing tests leads to a more testable code and a more testable code is cleaner. I wouldn't argue about this, I just wan't to note, that if you can write a testable code, then you also can write a non-testable one. And this looks like the case.

    So although you specifically say that

    should not have any test specific code in it

    I would ague that, while you probably shouldn't use createMemoryHistory as @aquinq suggested, or put anything else specifically and only for testing purposes, you can and probably should modify your code to be more testable.

    You can:

    1. Move <Router> higher. You can even wrap the <App> with it - it's the simplest and a recommended way, although may not apply to your case. But still I don't see why can't you put <div className={"Test"}> inside the <Router> and not vice versa.
    2. In your tests you are not supposed to test third-party libraries, you supposed to test your own code, so you can extract this
      <Switch>
        <Route path={"/A"}>
          <div className={"A"}>A</div>
        </Route>
        <Route path={"/B"}>
          <div>B</div>
        </Route>
      </Switch>
      
      part into a separate component and test it separately.
    3. Or if we combine these two: put <div className={"Test"}> inside the <Router>, extract <div className={"Test"}> into a separate component, write
      wrapper = mount(
        <MemoryRouter initialEntries={["/A"]}>
          <TestDiv/>
        </MemoryRouter>
      )
      
    4. Also createMemoryHistory can be a useful feature on it's own. And some time in the future you'll find yourself using it. In that case @aquinq's answer will do.
  3. But if you can't/don't want to modify your code at all. Then you can cheat a little and try this approach: How to test a component with the <Router> tag inside of it?

x00
  • 13,643
  • 3
  • 16
  • 40
  • Please understand that is the same as what I mean. I put the whole raw component in the test case in order to illustrate it better. interesting points, but If you can get my test to pass I will give you my bounty. – Oliver Watkins Jun 16 '20 at 10:00
  • @OliverWatkins, So, I don't get it: you've answered your own question following the advice from the link I've posted. But as you leaved this comment and ":/" in your answer, and also I don't know how to figure out exact times of your answer and your comment... is the question answered or not? – x00 Jun 16 '20 at 11:45
  • And yes, I do understand that `` is the component in question, and knowing that I've recommended to change it... or to use the advice from the link, which you did, that's also OK. – x00 Jun 16 '20 at 11:48
  • its half answered, i will give you half the bounty. The :/ means that i now have to figure out how to interactive navigate the routes in the test. thanks anyway – Oliver Watkins Jun 16 '20 at 14:04
  • this is what I mean by the :/ - ie. i have a continuation question : https://stackoverflow.com/questions/62411156/cannot-test-redirecting-in-react-router-with-enzyme – Oliver Watkins Jun 16 '20 at 14:41
2

According to documentation, if you use a regular Router in your test, you should pass a history prop to it

While you may be tempted to stub out the router context yourself, we recommend you wrap your unit test in one of the Router components: the base Router with a history prop, or a <StaticRouter>, <MemoryRouter>, or <BrowserRouter>

Hope this will work. If not, maybe using a second MemoryRouter instead of Router will simply do the job.

Community
  • 1
  • 1
aquinq
  • 1,318
  • 7
  • 18
1

Typically Router will be outside of the app logic, and if you're using other <Route> tags, then you could use something like <Switch>, like this:

  <Router>
    <Switch>
      <Route exact path="/">
        <HomePage />
      </Route>
      <Route path="/blog">
        <BlogPost />
      </Route>
    </Switch>
  </Router>

MemoryRouter actually is a Router, so it may be best to replace the "real" Router here. You could split this into a separate component for easier testing.

According to the source GitHub:

The most common use-case for using the low-level <Router> is to synchronize a custom history with a state management lib like Redux or Mobx. Note that this is not required to use state management libs alongside React Router, it's only for deep integration.

import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";

const history = createBrowserHistory();

ReactDOM.render(
  <Router history={history}>
    <App />
  </Router>,
  node
);

From personal experience:

I have used an outer component (we called it "Root") that includes the <Provider> and <Router> components at the top level, then the <App> includes just the <Switch> and <Route> components.

Root.jsx returns:

<Provider store={rootStore}>
  <Router history={rootHistory}>
    <App />
  </Router>
</Provider>

and App.jsx returns:

<Switch>
  <Route exact path="/" component={HomePage}>
  <Route exact path="/admin" component={AdminPage}>
</Switch>

This allows the App.test.jsx to use:

mount(
  <Provider store={fakeStore}>
    <MemoryRouter initialEntries={['/']}>
      <App myProp={dummyProp} />
    </MemoryRouter>
  </Provider>
)
aarowman
  • 153
  • 1
  • 8