Skip to content

Mock useParams (React Router 6+) in Jest with NO boilerplate

Posted on:July 23, 2023 at 09:25 AM

Intro

As a testing junkie developer who is very enthusiastic about writing tests (unit, integration, E2E, you name it), I try to test as much as I can.
A short while ago, while working with React Router in a hobby project of mine, I encountered the problem of trying to mock useParams() in Jest.
That’s not as easy as it sounds, because React Router doesn’t make this very easy for you, starting with the fact it’s not typed.

The question

Then, how to mock useParams() in Jest so we can provide test cases with the URL parameters we need?
There are ways to do that but they all require a lot of boilerplate, which seems typical when testing code that involves React Router.
Let’s refine the question, then: how to mock useParams() in Jest without unneeded boilerplate?

The answer

The answer is rather obscure: we import React Router as an object. This is the only way, apparently (or one of the very few) that allow Jest’s spyOn() to mock its hooks.
The following is an example with a component, a router and a Jest test so you can get a good idea of how this works in practice with a real parameter.

Example

Component

import { useParams } from "react-router-dom";

/**
 * Custom hook that checks for undefined-ness
 * of useParams() return values.
 *
 * @returns the shopping list id passed by React Router
 *          via dynamic segment `:list_id`
 */
export function useVerifiedParams(): string {
  const { list_id } = useParams();
  if (list_id === undefined) {
    throw new Error("Parameter 'list_id' is undefined");
  }
  return list_id;
}

function ShoppingList(): JSX.Element {
  const list_id = useVerifiedParams();
	...
	render(...);
}

index.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "@src/components/App";
import ShoppingList from "@src/components/ShoppingList";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "/lists/:list_id",
        element: <ShoppingList />,
        ...
      },
    ],
  },
]);

const container = document.getElementById("app") as HTMLDivElement;
createRoot(container).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>
);

Test

// Full import as object, required by spyOn to mock its hooks
import * as react_router from "react-router";
import ShoppingList from "@src/components/ShoppingList";

test("etc etc", () => {
  jest.spyOn(react_router, "useParams").mockReturnValueOnce({ list_id: "weekly" });

  /* Render of the component and test assertions */
  render(<ShoppingList />);
  ...
})