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.
Memory Router: Memory router keeps the URL changes in memory not in the user browsers. It keeps the history of the URL in memory (does not read or write to the address bar so the user can not use the browser's back button as well as the forward button. It doesn't change the URL in your browser.
You can use the createMemoryHistory function and Router component to test it. Create a memory history with initial entries to simulate the current location, this way we don't rely on the real browser environment. After firing the click event, assert the pathname is changed correctly or not.
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:
[...] it will read the current context value from the closest matching
Provider
above it in the tree.
<Router>
is a Provider
but not a Consumer
, so it can't peek up props from a parent <Router>
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:
<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.<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.<div className={"Test"}>
inside the <Router>
, extract <div className={"Test"}>
into a separate component, write
wrapper = mount(
<MemoryRouter initialEntries={["/A"]}>
<TestDiv/>
</MemoryRouter>
)
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.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?
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.
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.
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>
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With