13

When testing components with <Link>s, for example in my answer to Recommended approach for route-based tests within routes of react-router, I often use the following pattern to get access to the current location for testing purposes:

const renderInRouter = () => {
  const history = createMemoryHistory();
  const wrapper = render(
    <Router history={history}>
      <MyPage />
    </Router>
  );
  return { ...wrapper, history };
}

This worked fine up to v5.3, but after upgrading to v6 I get:

 FAIL  ./demo.test.js
  ✕ works (205 ms)

  ● works

    TypeError: Cannot read properties of undefined (reading 'pathname')

      at Router (../packages/react-router/index.tsx:281:5)

      ...

This use-case isn't covered in the migration docs, v6 has no testing guides so far and, although the API reference does show that the history prop is no longer expected:

interface RouterProps {
  basename?: string;
  children?: React.ReactNode;
  location: Partial<Location> | string;
  navigationType?: NavigationType;
  navigator: Navigator;
  static?: boolean;
}

it's not clear what the v6 equivalent is; I tried switching to navigator={history} but still got the same error.

To reproduce, copy the following files into a new directory and run npm install then npm test:

package.json

{
  "name": "router6-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "testEnvironment": "jsdom"
  },
  "babel": {
    "presets": [
      "@babel/preset-react"
    ]
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/preset-react": "^7.16.0",
    "@testing-library/react": "^12.1.2",
    "jest": "^27.3.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^6.0.0"
  }
}

index.test.js:

const { render } = require("@testing-library/react");
const { createMemoryHistory } = require("history");
const React = require("react");
const { Router } = require("react-router-dom");

it("used to work", () => {
  render(
    <Router history={createMemoryHistory()}></Router>
  );
});

If you npm install --save-dev react-router@5 and run npm test again, you can see that this passes in v5.

0

4 Answers 4

44

React Router v6 splits apart the history into multiple pieces, for this use case the relevant parts are the navigator and the location. This change is hinted at in Use useNavigate instead of useHistory, and you can see it in the definition of the Navigator type used in the Router props:

export declare type Navigator = Omit<History, "action" | "location" | "back" | "forward" | "listen" | "block">;

Just changing history={history} to navigator={history} still left the location prop, from which the router was trying to access the pathname (among other properties), undefined. To get the test working again, update the rendering as follows:

const { render } = require("@testing-library/react");
const { createMemoryHistory } = require("history");
const React = require("react");
const { Router } = require("react-router-dom");

it("works", () => {
  const history = createMemoryHistory();
  render(
    <Router location={history.location} navigator={history}></Router>
  );
});

Note that from React Router 6.4 history is no longer included as a dependency, so if you hadn't explicitly installed it you will need to add it back in.

Sign up to request clarification or add additional context in comments.

1 Comment

This didn't work for me.
5

Just in case someone is stucked on this and can't figure out what is happening after trying all of the solutions posted here, please make sure that you are importing the right Router:

if you have this: import { Router } from "react-router-dom";

change the import for: import { BrowserRouter as Router } from "react-router-dom";

Comments

2

This is my solution to the issue, from a course I have been following on Udemy:

const { render } = require("@testing-library/react");
const { createBrowserHistory } = require("history");
const React = require("react");
const { unstable_HistoryRouter: HistoryRouter } = require("react-router-dom");

it("works", () => {
  const history = createBrowserHistory();
  render(
    <HistoryRouter history={history}></HistoryRouter>
  );
});

2 Comments

This was explicitly flagged unstable_ as a "new, undocumented API that will likely introduce bugs". It's probably best not to write code relying on it until it becomes stable.
Ok thanks, I will make sure to keep that in mind, I did so as I tried your solution, and my URL changed, but those changes were not replicated on the app component, probably due to the SPA nature of the app.
0

if you are using react-router-dom version greater than 6 and your import looks like this

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

then try changing into this

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.