Search code examples
reactjsjestjsreact-routerreact-router-domreact-testing-library

"Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router


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.


Solution

  • 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.