How to test redirections and history state with Jest

How to test redirections and history state with Jest

Crysfel Villa Programming

Testing redirections in jest using react router is not a straight forward process, specially when using hooks. In this tutorial I'm going to show you how to test a redirect, in addition we are going to test the state in the history.

The code we will be testing is a button, when clicked we will redirect the user to a new page using react router, but we are also going to set some data into the history state.

import React from 'react';
import { useNavigate, useLocation } from 'react-router-dom';

export default function Page() {
  const navigate = useNavigate();
  const location = useLocation();
  const onCheckout = () => {
    navigate('/billing/checkout', {
      state: { previous: location.pathname },
    });
  };

  return (
    <div>
      <p>Click to redirect</p>
      <button onClick={onCheckout}>Checkout</button>
    </div>
  );
}

To write an effective test, we need to first click the button and expect the navigate function gets called with the right URL and state.

The first challenge here are the hooks, we need to mock those hooks so we can check how they were called.

jest.mock('react-router-dom', () => {
  return {
    ...jest.requireActual('react-router-dom'),
    useLocation: jest.fn(),
    useNavigate: jest.fn(),
  };
});

const Router = require('react-router-dom');

Every time those hooks are called, from the react component we are testing, they will return a mock function instead.

But these hooks return some values our component relies on, therefore we need to mock those too as well.

jest.mock('react-router-dom', () => {
  const nav = jest.fn();
  return {
    ...jest.requireActual('react-router-dom'),
    mockedNavigation: nav,
    useLocation: jest.fn(() => ({ pathname: '/example' })),
    useNavigate: jest.fn(() => nav),
  };
});

const Router = require('react-router-dom');

The useLocation is pretty simple, just returning an object with a pathname property.

useNavigate on the other hand, requires to define a mock function to return but also we need to access that reference in our test to validate it was called with the right arguments.

That's why we have created a new mockedNavigation property, so we can have access to that property.

The next thing to do is to write the actual test as follow.

import React from 'react';
import { render, screen } from '@testing-library/react';
import Page from './Page';

jest.mock('react-router-dom', () => {
  const nav = jest.fn();
  return {
    ...jest.requireActual('react-router-dom'),
    mockedNavigation: nav,
    useLocation: jest.fn(() => ({ pathname: '/example' })),
    useNavigate: jest.fn(() => nav),
  };
});

const Router = require('react-router-dom');

describe('Page', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('redirects to checkout page', async () => {
    render(
      <Router.MemoryRouter initialEntries = {['/example'] }>
        <Router.Routes>
          <Router.Route path="example" element={<Page />}>
        <Router.Routes>
      </Router.MemoryRouter>
    );

    await userEvent.click(await screen.findByText('Checkout'));

    expect(Router.mockedNavigation).toHaveBeenCalledWith(
      '/billing/checkout', 
      {
        state: { previous: '/example' },
      }
    );
  });
});

We are using the memory router to set up our test, we only need a single route to test in this case.

The second step is to click the button, we do this by firing the click event on the checkout button.

Finally, we make sure the mocked function was called with the right parameters, that is the URL and the state.

That's it folks, I hope this tutorial helps you to write better tests, it took me some time to get this working correctly.

Happy Coding!

Did you like this post?

If you enjoyed this post or learned something new, make sure to subscribe to our newsletter! We will let you know when a new post gets published!

Article by Crysfel Villa

I'm a Sr Software Engineer who enjoys crafting software, I've been working remotely for the last 10 years, leading projects, writing books, training teams, and mentoring new devs. I'm the tech lead of @codigcoach