31

I am trying to mock history.push inside the new useHistory hook on react-router and using @testing-library/react. I just mocked the module like the first answer here: How to test components using new react router hooks?

So I am doing:

//NotFound.js
import * as React from 'react';
import { useHistory } from 'react-router-dom';


const RouteNotFound = () => {
  const history = useHistory();
  return (
    <div>
      <button onClick={() => history.push('/help')} />
    </div>
  );
};

export default RouteNotFound;
//NotFound.test.js
describe('RouteNotFound', () => {
  it('Redirects to correct URL on click', () => {
    const mockHistoryPush = jest.fn();

    jest.mock('react-router-dom', () => ({
      ...jest.requireActual('react-router-dom'),
      useHistory: () => ({
        push: mockHistoryPush,
      }),
    }));

    const { getByRole } = render(
        <MemoryRouter>
          <RouteNotFound />
        </MemoryRouter>
    );

    fireEvent.click(getByRole('button'));
    expect(mockHistoryPush).toHaveBeenCalledWith('/help');
  });
})

But mockHistoryPush is not called... What am I doing wrong?

slideshowp2
  • 38,463
  • 29
  • 127
  • 255
Albert Alises
  • 668
  • 1
  • 5
  • 15

2 Answers2

71

Use jest.mock in module scope will automatically be hoisted to the top of the code block. So that you can get the mocked version react-router-dom in NotFound.jsx file and your test file.

Besides, we only want to mock useHistory hook, so we should use jest.requireActual() to get the original module and keep other methods as the original version.

Here is the solution:

NotFound.jsx:

import React from 'react';
import { useHistory } from 'react-router-dom';

const RouteNotFound = () => {
  const history = useHistory();
  return (
    <div>
      <button onClick={() => history.push('/help')} />
    </div>
  );
};

export default RouteNotFound;

NotFound.test.jsx:

import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render, fireEvent } from '@testing-library/react';
import RouteNotFound from './NotFound';

const mockHistoryPush = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: mockHistoryPush,
  }),
}));

describe('RouteNotFound', () => {
  it('Redirects to correct URL on click', () => {
    const { getByRole } = render(
      <MemoryRouter>
        <RouteNotFound />
      </MemoryRouter>,
    );

    fireEvent.click(getByRole('button'));
    expect(mockHistoryPush).toHaveBeenCalledWith('/help');
  });
});

Unit test result with 100% coverage:

PASS  src/stackoverflow/58524183/NotFound.test.jsx
  RouteNotFound
    ✓ Redirects to correct URL on click (66ms)

--------------|----------|----------|----------|----------|-------------------|
File          |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files     |      100 |      100 |      100 |      100 |                   |
 NotFound.jsx |      100 |      100 |      100 |      100 |                   |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.133s, estimated 11s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58524183

slideshowp2
  • 38,463
  • 29
  • 127
  • 255
  • @slidershowp2 can you please help me in https://stackoverflow.com/questions/61928263/how-to-write-test-case-for-url-push-in-useeffect-react-hook – Enigmatic May 21 '20 at 06:45
  • Hey @slideshowp2, thanks for this answer. I was trying to do this but I had the `jest.mock` inside of the `describe` block and it wasn't working. Do you know why this is the case? I found this thread but I still don't understand it. https://github.com/facebook/jest/issues/2582 – AliF50 Dec 31 '20 at 02:09
  • You saved me. Thank you very much – minh.duc Feb 25 '21 at 08:12
  • I'm having the same issue expect my component using `RouteComponentProps`along with the `useHIstory()` hook. The hook returns undefined with the components inside the test. Is there a solution for mocking `useHistory()` with a component that uses `RouteComponentProps`? – Joseph Freeman May 14 '21 at 15:19
3

You actually do not need to mock react-router-dom (at least for v5) as it provides a bunch of testing tools: https://reactrouter.com/web/guides/testing

To check your history is actually changed, you can use createMemoryHistory and inspect its content:

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Menu } from './Menu';
import { createMemoryHistory } from 'history'
import { Router } from 'react-router-dom';

test('triggers path change', () => {
  const history = createMemoryHistory();

  render(
    <Router history={history}>
      <Menu />
    </Router>
  );

  const aboutItem = screen.getByText('About');
  expect(aboutItem).toBeInTheDocument();

  userEvent.click(aboutItem);
  expect(history.length).toBe(2);
  expect(history.location.pathname).toBe('/about');
});
Soullivaneuh
  • 2,074
  • 1
  • 22
  • 49