How to mock useHistory hook in jest?

ReactjsTypescriptReact RouterJestjsEnzyme

Reactjs Problem Overview


I am using UseHistory hook in react router v5.1.2 with typescript? When running unit test, I have got issue. > TypeError: Cannot read property 'history' of undefined.

import { mount } from 'enzyme';
import React from 'react';
import {Action} from 'history';
import * as router from 'react-router';
import { QuestionContainer } from './QuestionsContainer';

describe('My questions container', () => {
    beforeEach(() => {
        const historyHistory= {
            replace: jest.fn(),
            length: 0,
            location: { 
                pathname: '',
                search: '',
                state: '',
                hash: ''
            },
            action: 'REPLACE' as Action,
            push: jest.fn(),
            go: jest.fn(),
            goBack: jest.fn(),
            goForward: jest.fn(),
            block: jest.fn(),
            listen: jest.fn(),
            createHref: jest.fn()
        };//fake object 
        jest.spyOn(router, 'useHistory').mockImplementation(() =>historyHistory);// try to mock hook
    });

    test('should match with snapshot', () => {
        const tree = mount(<QuestionContainer />);

        expect(tree).toMatchSnapshot();
    });
});

Also i have tried use jest.mock('react-router', () =>({ useHistory: jest.fn() })); but it still does not work.

Reactjs Solutions


Solution 1 - Reactjs

I needed the same when shallowing a react functional component that uses useHistory.

Solved with the following mock in my test file:

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

Solution 2 - Reactjs

This one worked for me:

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

Solution 3 - Reactjs

Here's a more verbose example, taken from working test code (since I had difficulty implementing the code above):

Component.js

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

  const Component = () => {
      ...
      const history = useHistory();
      ...
      return (
          <>
              <a className="selector" onClick={() => history.push('/whatever')}>Click me</a>
              ...
          </>
      )
  });

Component.test.js

  import { Router } from 'react-router-dom';
  import { act } from '@testing-library/react-hooks';
  import { mount } from 'enzyme';
  import Component from './Component';
  it('...', () => {
    const historyMock = { push: jest.fn(), location: {}, listen: jest.fn() };
    ...
    const wrapper = mount(
      <Router history={historyMock}>
        <Component isLoading={false} />
      </Router>,
    ).find('.selector').at(1);

    const { onClick } = wrapper.props();
    act(() => {
      onClick();
    });

    expect(historyMock.push.mock.calls[0][0]).toEqual('/whatever');
  });

Solution 4 - Reactjs

Wearing my politician hat I'll dare to state that you're asking the wrong question.

It's not useHistory that you want to mock. Instead you'd just want to feed it with history object which you control.

This also allows you to check for push invocations, just like the 2 top answers (as of writing this).

If that's indeed the case, createMemoryHistory got your back:

import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'

test('QuestionContainer should handle navigation', () => {
  const history = createMemoryHistory()
  const pushSpy = jest.spyOn(history, 'push') // or 'replace', 'goBack', etc.
  render(
      <Router history={history}>
        <QuestionContainer/>
      </Router>
  )
  userEvent.click(screen.getByRole('button')) // or whatever action relevant to your UI
  expect(pushSpy).toHaveBeenCalled()
})

Solution 5 - Reactjs

In github react-router repo i found that useHistory hook uses singleton context, when i started use in mount MemoryRouter it found context and started works. So fix it

import { MemoryRouter } from 'react-router-dom';
const tree =  mount(<MemoryRouter><QuestionContainer {...props} /> </MemoryRouter>);

Solution 6 - Reactjs

A way to mock the push function of useHistory:

import reactRouterDom from 'react-router-dom';
jest.mock('react-router-dom');

const pushMock = jest.fn();
reactRouterDom.useHistory = jest.fn().mockReturnValue({push: pushMock});

Then, how to check if the function have been called:

expect(pushMock).toHaveBeenCalledTimes(1);
expect(pushMock).toHaveBeenCalledWith('something');

Solution 7 - Reactjs

This works for me, I was having problems with useLocation too

jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    push: jest.fn()
  }),
  useLocation: jest.fn().mockReturnValue({
    pathname: '/another-route',
    search: '',
    hash: '',
    state: null,
    key: '5nvxpbdafa'
})}))

Solution 8 - Reactjs

I found the above answers very helpful. However I missed the ability to spy and actually test functionality. But simply naming the mock function first solved that for me.

const mockPush = jest.fn();
jest.mock('react-router-dom', () => ({
  useHistory: () => {
    const push = () => mockPush ();
    return { push };
  },
}));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIvan MartinyukView Question on Stackoverflow
Solution 1 - ReactjsProustibatView Answer on Stackoverflow
Solution 2 - ReactjsErhanView Answer on Stackoverflow
Solution 3 - ReactjsAlex WView Answer on Stackoverflow
Solution 4 - ReactjstargumonView Answer on Stackoverflow
Solution 5 - ReactjsIvan MartinyukView Answer on Stackoverflow
Solution 6 - ReactjsSamuelView Answer on Stackoverflow
Solution 7 - ReactjsHenry TipantuñaView Answer on Stackoverflow
Solution 8 - ReactjsrogerdeanView Answer on Stackoverflow