'Simplest test for react-router's Link with @testing-library/react

I'm trying to understand how to best test that react-router behaves as expected with @testing-library/react.

The simplest test I can think of, is to verify that clicking a link changes the URL. I know that ideally I should test that clicking on a link renders a new component, but that adds a lot of boilerplate to the test.

So here's my failing example:

import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';

it('routes to a new route', async () => {
  const history = createMemoryHistory();
  const { getByText } = render(
    <MemoryRouter history={history}>
      <Link to="/hello">Click me</Link>
    </MemoryRouter>
  );

  fireEvent.click(getByText('Click me'));
  await waitFor(() => expect(history.location.pathname).to.equal('/hello')); // Fails
});


Solution 1:[1]

This is how I do it : mocking history.push, then spy on its calls.

import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';

it('routes to a new route', async () => {
  const history = createMemoryHistory();

  // mock push function
  history.push = jest.fn();

  const { getByText } = render(
    <MemoryRouter history={history}>
      <Link to="/hello">Click me</Link>
    </MemoryRouter>
  );

  // could be userEvent.click
  // https://testing-library.com/docs/ecosystem-user-event/#clickelement-eventinit-options
  fireEvent.click(getByText('Click me'));

  // spy on push calls, assert on url (parameter)
  expect(history.push).toHaveBeenCalledWith('/hello');
});

Solution 2:[2]

With Typescript and "react-router-dom": "^6.3.0", the <MemoryRouter /> component does not accept the history prop.

This doesn't work <MemoryRouter history={history} >

I have worked around it with the example below.

import { render, screen, fireEvent } from '@testing-library/react';
import {  createMemoryHistory } from 'history';
import { Router, Link } from 'react-router-dom';

it('routes to a new route', () => {
  const history = createMemoryHistory();
  history.push = jest.fn();

  render(
    <Router location={history.location} navigator={history}>
      <Link to="/hello">Click Me</Link>
    </Router>,
  );

  fireEvent.click(screen.getByText(/Click Me/i));

  expect(history.push).toHaveBeenCalledWith(
    {
      hash: '',
      pathname: '/hello',
      search: '',
    },
    undefined,
  );
});

Solution 3:[3]

You do not have to use link to change the URL, you can simply use Route.

Sample:

it('should change the url', () => {
        expect.assertions(1);
        act(() => {
            render(
                <BrowserRouter>
                    <ProtectedRoute path="/mockPage" component={MyPage} />
                    <Route render={() => <Redirect to="/mockPage" />} />
                </BrowserRouter>
            );
        });
        expect(screen.getByTestId('an-element-in-myPage')).toBeInTheDocument();
    });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Pierre
Solution 3 user3184564