Paul Cowan Contract software developer.

Testing the React Router useNavigate Hook with React Testing Library

5 min read 1472

Testing The React Router UseNavigate Hook With React Testing Library

Editor’s note: This article was last updated by Rahul Chhodde on 4 May 2023 to include information the React fireEvent and userEvent Hooks, as well as testing the React query parameters.

React Testing Library is a lightweight solution that provides a virtual DOM to interact with and verify the behavior of a React component. Rather than work like a test runner, this testing library requires a tool like Jest to implement automated testing in React.

Using React Testing Library, popularly known as RTL in the community, is the recommended way to test your apps because it is actively maintained, well-documented, fast, flexible, and powerful enough to write comprehensive and reliable tests.

From React 18 onwards, React Hook Testing Library is included in the React Testing Library, and you can access it only through RTL. Learn more here.

In this article, we will cover testing scenarios of the useNavigate Hook from React Router 6 with RTL and Jest. We will unit test the routes and briefly discuss the different features that RTL offers to simplify the process of making components route-aware.

Jump ahead:

Understanding the useNavigate Hook

React Router v6+ now uses the useNavigate Hook instead of the useHistory Hook. You can use useNavigate to go to a specific path or move back and forth in the browser history.

To add routes to your React app, you need to install the react-router-dom package. Once you have installed the package, you can import the Route component from it. The Route component takes two props: path and component. The path prop is the URL path that the route should match, and the component prop is the component that should be rendered when the route matches.

For example, the following code would add a route that matches the / path and renders the Home component:

import { Routes, Route } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";

function App() {
  return (
    <>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </>
  );
}
export default App;

Let’s implement the useNavigate Hook. The example below shows how easy it is to navigate to other routes by using the useNavigate Hook:

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

function HomeButton() {
  const navigate = useNavigate();

  function handleClick() {
    navigate("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

Here is the live, working version of our project, which also includes the rest of the examples we covered in this article. See it on CodeSandbox here.

If you add { replace: true } as a second argument when using the useNavigate Hook, it works like the older history.replace method. To move back and forth in the history stack, use the following approach:

function HomeButton() {
  const navigate = useNavigate();

  const goBack() => navigate(-1);
  const goForward() => navigate(1);
}

Testing the useNavigate Hook with jest.mock

Avoid using jest.mock to test React routes, as React Testing Library offers various methods to test routes without mocking. You can use the render method to isolate a route, the getBy* method to locate elements in the rendered page, and the fireEvent and userEvent Hooks to trigger different events.

Testing React routes and the useNavigate Hook

As discussed before, React Router 6 and above now uses the useNavigate Hook instead of the useHistory hook. You can no longer set the history prop to the router anymore. The history API is buggy and must have been discontinued in the newer version of React in favor of simplifying the complicated routing practices, so relying on it for routing is not a good idea.

Here’s a straightforward application to show you how to test routes with useNavigate. The application has different route components, and we used RTL and Jest features to test each one:

import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { MemoryRouter, BrowserRouter } from "react-router-dom";
import { About } from "./About";

describe("Demonstrating some useNavigate() tests ", () => {
  it("Renders the About component", () => {
    const { getByText } = render(
      <BrowserRouter>
        <About />
      </BrowserRouter>
    );

    const button = getByText(/contact us/i);
    expect(button).toBeInTheDocument();
    fireEvent.click(button);
  });
});

The above is a test component for the “About” section of our app. Keep in mind that all test components must be in the same directory as the original component so that Jest can detect our tests.

We use the describe and it methods from Jest to describe our tests and add some sample assertions. To locate an element with a specific text fragment from the rendered component, we use the RTL getByText utility. If the text fragment is not found, you’ll see an error while testing.

Next, we use fireEvent to click the selected button element. You can customize these tests further to fit your own needs.

Testing query parameters with useLocation

We now know how to test the regular React routes with RTL and Jest. Let’s test a route with query parameters with the help of the useLocation Hook.

We can grab the current URL with the useLocation Hook, then get the query string and extract the value from the query parameter using URLSearchParams, which is part of the Web API and Node.js API:

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

const QueryParamComponent = () => {
  const location = useLocation();
  const id = new URLSearchParams(location.search).get("id");

  return (
    <div>
      <h1>Query Parameter Component</h1>
      <p>ID: {id}</p>
    </div>
  );
};
export default QueryParamComponent;

After extracting the value of the id query parameter from the URL, we added it to the resulting DOM element of this component. Now, let’s add a test for this:

import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import QueryParamComponent from "./QueryParamComponent";
import "@testing-library/jest-dom";

describe("MyComponent", () => {
  it("renders the ID query parameter from the URL", () => {
    render(
      <MemoryRouter initialEntries={["/qpc?id=123"]}>
          <QueryParamComponent />
      </MemoryRouter>
    );
    expect(screen.getByText("ID: 123")).toBeInTheDocument();
  });
});

In the above code, we used MemoryRouter to create a mock router that we could use to test the behavior of the QueryParamComponent when it receives a query parameter through the location prop. We set its initial path to /query with an id query parameter so that our component would receive this information when it was rendered. Again, you can see this for yourself in the CodeSandbox demo’s tests.

Using the fireEvent and userEvent methods

The fireEvent method can dispatch any DOM event to an element, which helps test how components respond to events. For instance, you can use it to test a button that logs an input field’s value when clicked.

In contrast, userEvent is a simpler method that mimics how users interact with a component. This is helpful when testing components designed for humans, such as a login form:

...
describe("Login component", () => {
  it("Filling details and clicking buttons in a login gorm", async () => {
    render(
      <BrowserRouter>
        <Login />
      </BrowserRouter>
    );

    userEvent.type(screen.getByLabelText("Username"), "hello");
    userEvent.type(screen.getByLabelText("Password"), "123");
    userEvent.click(screen.getByRole("button", { name: "Submit" }));
  });
});

To use the userEvent object, you need to install the @testing-library/user-event package as an additional dependency.



Testing hooks with React Testing Library

As I mentioned earlier, you don’t need to use the @testing-library/react-hooks library anymore with React 18 and above. The RTL now includes built-in methods for testing hooks.

It’s fairly easy to test different hooks using the RTL built-in methods only. Here is an example of how to test a custom hook created with useState, demonstrating a dynamic count component that increments with a button click:

describe("CountComponent", () => {
  it("SHOULD render the expected Count Component", () => {
    const { getByText } = render(
      <BrowserRouter>
        <CountComponent />
      </BrowserRouter>
    );
    const heading = getByText(/the count component/i);
    const count = getByText(/the count is 0/i);

    expect(heading).toBeInTheDocument();
    expect(count).toBeInTheDocument();
  });

  it("SHOULD increment the count when the button is CLICKED", () => {
    const { getByText } = render(
      <BrowserRouter>
        <CountComponent />
      </BrowserRouter>
    );

    const button = getByText(/increment Count/i);
    const count = getByText(/the count is 0/i);
    fireEvent.click(button);
    expect(count).toHaveTextContent(/the count is 1/i");
  });
});

This component and its test are only available in the CodeSandbox example.

Conclusion

With the ever-changing React ecosystem, there are new tools available to help you write better tests. I don’t recommend using Jest mocks to test routes, as this can give the illusion of testing without actually testing anything.

In this article, we explored how to test the useNavigate Hook that was introduced in React Router v6. I hope you enjoyed this article, and feel free to leave a comment if you have any questions. Happy testing!

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Paul Cowan Contract software developer.

2 Replies to “Testing the React Router useNavigate Hook with React Testing…”

    1. This. I was enthusiastic when I read the article was written in 2022, hoping that it would cover react router v6.

Leave a Reply