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

Testing React Component with React Router V6


I understand that React Testing Library has an example of testing with react router, but I couldn't get it to work (I think because I am using react router V6).

Basically, I need router testing because I have details component that uses useParams() to get part of the url. I can't render the component without it.

This was my attempt to make it work (yes the page also needs apollo, although it doesn't really need redux).

const AllTheProviders = ({children, initialRoutes}) => {
    return (
        <ApolloProvider client={client}>
            <Provider store={store}>
                <MemoryRouter>
                    {children}
                </MemoryRouter>
            </Provider>
        </ApolloProvider>
    );
}
const customRender = (ui, options) => render(ui, {wrapper: AllTheProviders, ...options})


beforeEach(() => {
    window.history.pushState({}, 'Test page',"/details/url-param-the-component-needs")
    customRender(<App/>);
});

No surprise, but this didn't work. I assume window.history.pushState() doesn't work for react router V6. I tried using useNavigate(), but that doesn't work outside of a component.

If anybody has any ideas on how I could make this work. I would greatly appreciate it.


Solution

  • The MemoryRouter still takes an array of initialEntries.

    MemoryRouter

    declare function MemoryRouter(
      props: MemoryRouterProps
    ): React.ReactElement;
    
    interface MemoryRouterProps {
      basename?: string;
      children?: React.ReactNode;
      initialEntries?: InitialEntry[];
      initialIndex?: number;
    }
    

    I would remove the MemoryRouter from the customRender and just wrap the component under test locally and pass in the specific initial route entries for the test.

    const AllTheProviders = ({ children }) => {
      return (
        <ApolloProvider client={client}>
          <Provider store={store}>
            {children}
          </Provider>
        </ApolloProvider>
      );
    };
    const customRender = (ui, options) => 
      render(ui, { wrapper: AllTheProviders, ...options });
    

    ...

    const { ....queries.... } = customRender(
      <MemoryRouter
        initialEntries={["Test page", "/details/url-param-the-component-needs"]}
      >
        <ComponentUnderTest />
      </MemoryRouter>
    );
    

    An additional thought, the useParams hook may also need a Route with a path prop specifying the match params the component needs, so your test could potentially look like the following:

    const { ....queries.... } = customRender(
      <MemoryRouter
        initialEntries={["Test page", "/details/url-param-the-component-needs"]}
      >
        <Routes>
          <Route path="/details/:theParam" element={<ComponentUnderTest />} />
        </Routes>
      </MemoryRouter>
    );