Home > Mobile >  How to test routing logic with React Router v6 and testing-library?
How to test routing logic with React Router v6 and testing-library?

Time:11-11

I migrated from React Router v5 to v6 following this tutorial to create an app. I want to test it with react-testing-library, but my old unit tests (using the pattern in this doc) stopped working.

My app with React Router v6 is like this

const router = createBrowserRouter([
    {
        path: "/",
        element: (
            <>
                <SiteHeader />
                <Outlet />
            </>
        ),
        errorElement: <NotFound />,
        children: [
            { path: "/", element: <Home /> },
            { path: "/posts", element: <Posts /> },
            { path: "/post/:postId", element: <PostPage /> },
        ],
    },
]);

function App() {
    return (
        <div className="app">
            <RouterProvider router={router} />
        </div>
    );
}

As you can see, it's using RouterProvider instead of Switch/Route (so I'm confused that this SO question says it's using React Router v6 but it looks so different.).

The code in official doc of testing-library is not using RouterProvider either.

I want to test some routing logic like this pseudo code:

renderWithRouter(<App />, "/posts"); // loads /posts page initially
await user.click(screen.getByText("some post title")); // trigger click
expect(getUrl(location)).toEqual("/post/123"); // checks the URL changed correctly

How can I create a renderWithRouter function like this with RouterProvider? Note that this renderWithRouter worked for me when I used React Router v5, but after migrating to v6, it stopped working.

My current dependency versions:

  • "react": "^18.2.0",
  • "react-dom": "^18.2.0",
  • "react-router-dom": "^6.4.3",
  • "@testing-library/jest-dom": "^5.16.5",
  • "@testing-library/react": "^13.4.0",
  • "@testing-library/user-event": "^14.4.3",

I tried this

test("click post goes to /post/:postId", async () => {
    render(
        <MemoryRouter initialEntries={["/posts"]}>
            <App />
        </MemoryRouter>,
    );
    // ...
});

but I got error

You cannot render a <Router> inside another <Router>. You should never have more than one in your app.

      31 | test("click post goes to /post/:postId", async () => {
    > 32 |     render(
         |     ^
      34 |         <MemoryRouter initialEntries={["/posts"]}>
      36 |             <App />

CodePudding user response:

If you want to test your routes configuration as a whole, using the new [email protected] Data Routers, then I'd suggest a bit of a refactor of the code to allow being able to stub in a MemoryRouter for any unit testing.

Declare the routes configuration on its own and export.

const routesConfig = [
  {
    path: "/",
    element: (
      <>
        <SiteHeader />
        <Outlet />
      </>
    ),
    errorElement: <NotFound />,
    children: [
      { path: "/", element: <Home /> },
      { path: "/posts", element: <Posts /> },
      { path: "/post/:postId", element: <PostPage /> },
    ],
  },
];

export default routesConfig;

In the app code import routesConfig and instantiate the BrowserRouter the app uses.

import {
  RouterProvider,
  createBrowserRouter,
} from "react-router-dom";
import routesConfig from '../routes';

const router = createBrowserRouter(routesConfig);

function App() {
  return (
    <div className="app">
      <RouterProvider router={router} />
    </div>
  );
}

For unit tests import the routesConfig and instantiate a MemoryRouter.

import {
  RouterProvider,
  createMemoryRouter,
} from "react-router-dom";
import { render, waitFor } from "@testing-library/react";
import routesConfig from '../routes';

...

test("click post goes to /post/:postId", async () => {
  const router = createMemoryRouter(routesConfig, {
    initialEntries: ["/posts"],
  });

  render(<RouterProvider router={router} />);

  // make assertions, await changes, etc...
});
  • Related