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

5

17

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
});
Neonate answered 18/5, 2020 at 12:45 Comment(1)
In react router v6 you can simply assert text on the target page as in link here: testing-library.com/docs/example-react-routerFecal
T
34

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');
});
Torquemada answered 18/5, 2020 at 14:29 Comment(1)
One thing that should be added is that now we need to import <Router /> instead of <MemoryRouter />.Arminius
E
15

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,
  );
});
Euphonium answered 27/4, 2022 at 7:23 Comment(0)
E
2

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();
    });
Encumbrance answered 11/5, 2022 at 17:17 Comment(0)
V
2

This is what worked for me (based on Florian Motteau's answer): react-router-dom: "^5.3.0"

🟡 The only difference is importing Route and Link from the react-router-dom instead of MemoryRouter.

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


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

  history.push = jest.fn();

  const { getByText } = render(
    <Router history={history}>
      <Link to="/path-to-navigate">Click me</Link>
    </Router>
  );

  fireEvent.click(getByText('Click me'));

  expect(history.push).toHaveBeenCalledWith('/path-to-navigate');
});

Vivienviviene answered 12/10, 2022 at 0:38 Comment(0)
T
0

With Typescript and "react-router-dom": "^6.24.0", for some reason, history.push from Pierre's answer is being called with a third parameter ,

Object {
+     "preventScrollReset": undefined,
+     "relative": undefined,
+     "replace": false,
+     "state": undefined,
+     "unstable_viewTransition": undefined,
+   }

although the type definition says that the function accepts 2 parameters.

So, the last expect should be

expect(history.push).toBeCalledWith(
    {
        pathname : to,
        search : '',
        hash : ''
    },
    undefined,
    expect.any(Object)
)
Tad answered 12/7, 2024 at 6:6 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.