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

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


I migrated from React Router v5 to v6 following this tutorial. 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 />

Solution

  • 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...
    });