How to mock history.push with the new React Router Hooks using Jest
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 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 5.133s, estimated 11s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58524183
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');});