reactjsreact-routerreact-testing-library

React Router Outlet Not Rendering in Nested Routes While Testing with React Testing Library After Upgrading to v7


import { render, screen, waitFor } from '@testing-library/react';
import React from "react";
import { createMemoryRouter, RouterProvider, Outlet, createBrowserRouter } from 'react-router-dom';

const Layout = () => (
  <div>
    <header>Header</header>
    <main>
      <Outlet />
    </main>
    <footer>Footer</footer>
  </div>
);


const routes = [
  {
    path: '/',
    element: <Layout />,
    children: [
      { path: '/', element: <>home</> },
      { path: 'about', element: <>about</> },
    ],
  },
];
const router = createBrowserRouter(routes);

function App() {
  return <RouterProvider router={router} />;
}



describe("sample code ", () => {
  it("test home", async () => {
    const router1 = createMemoryRouter(routes, { initialEntries: ['/about'] })
    render(
      <RouterProvider router={router1} />
    );

    expect(screen.getByText(/header/i)).toBeInTheDocument();
    await waitFor(() => expect(screen.getByText(/about/i)).toBeInTheDocument());
    expect(screen.getByText(/footer/i)).toBeInTheDocument();
  })
})

I'm currently testing routes and nested routes using initial entries in React Testing Library (RTL). However, I'm encountering an issue where the Outlet component isn't functioning as expected in my tests, even though it works perfectly in the actual application.


Solution

  • I was initially able to reproduce the testing issue you described. The same code you have here works perfectly well in React-Router-DOM 6, so something non-trivial, but very subtle, changed in the major version update from v6 to v7.

    I checked:

    It was in the migration guide where they discuss in better detail the deprecations and changes to be made from v6 to v7. Specifically the "Update DOM-specific imports" section at the end that describes where RouterProvider is exported from in the repo.

    👉 Update DOM-specific imports

    RouterProvider and HydratedRouter come from a deep import because they depend on "react-dom":

    -import { RouterProvider } from "react-router-dom";
    +import { RouterProvider } from "react-router/dom";
    

    Note you should use a top-level import for non-DOM contexts, such as Jest tests:

    -import { RouterProvider } from "react-router-dom";
    +import { RouterProvider } from "react-router";
    

    Congratulations, you're now on v7!

    In your unit test code, import the router components from react-router instead of react-router-dom.

    import { render, screen } from "@testing-library/react";
    import "@testing-library/jest-dom";
    import React from "react";
    import {
      createMemoryRouter,
      RouterProvider,
      Outlet,
      createBrowserRouter,
    } from "react-router"; // <-- Use main React-Router package!
    
    const Layout = () => (
      <div>
        <header>Header</header>
        <main>
          <Outlet />
        </main>
        <footer>Footer</footer>
      </div>
    );
    
    const routes = [
      {
        path: "/",
        element: <Layout />,
        children: [
          { path: "/", element: <>home</> },
          { path: "about", element: <div>about</div> },
        ],
      },
    ];
    const router = createBrowserRouter(routes);
    
    function App() {
      return <RouterProvider router={router} />;
    }
    
    describe("sample code ", () => {
      it("test home", async () => {
        const router1 = createMemoryRouter(routes, { initialEntries: ["/about"] });
        render(<RouterProvider router={router1} />);
    
        expect(screen.getByText(/header/i)).toBeInTheDocument();
        expect(screen.getByText(/about/i)).toBeInTheDocument();
        expect(screen.getByText(/footer/i)).toBeInTheDocument();
      });
    });
    

    Even though @remix/react-router re-exports all of react-router-dom, this doesn't include the RouterProvider which is located in another repo package. Using react-router-dom alone doesn't get you the correct imports.

    imports from react-router-dom imports from react-router
    imports from react-router-dom imports from react-router