How to mock useHistory hook in jest?

42,659

Solution 1

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

This one worked for me:

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

Solution 3

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

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 5

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 };
  },
}));
Share:
42,659
Ivan Martinyuk
Author by

Ivan Martinyuk

Updated on February 14, 2022

Comments

  • Ivan Martinyuk
    Ivan Martinyuk about 2 years

    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.

  • Pnar Sbi Wer
    Pnar Sbi Wer over 4 years
    this approach preserves the other react-router-dom functions which you may not want to mock
  • Mukund Kumar
    Mukund Kumar about 4 years
    @Erhan i have done the same. but again it is throwing error : TypeError: Cannot read property 'history' of undefined. any suggestion ?
  • Hiroki
    Hiroki almost 4 years
    For those who use TypeScript, this approach may cause the "React.createElement: type is invalid — expected a string" error if the component uses Link and useHistory at the same time. Erhan's approach won't cause that issue.
  • wentjun
    wentjun almost 4 years
    This won't work in TypeScript, as it will give the following error: TS2698: Spread types may only be created from object types.
  • taystack
    taystack over 3 years
    Is there a way to capture useHistory().push() invocations?
  • Jonathan Reyes
    Jonathan Reyes over 3 years
    For TypeScript support, see this answer.
  • omeralper
    omeralper over 3 years
    But how do you spyOn useHistory function?
  • wentjun
    wentjun over 3 years
    Those who are using TypeScript may refer to this: stackoverflow.com/q/62774929/10959940 :)
  • Amit Kumar
    Amit Kumar over 3 years
    @proustibat, can you provide a bit elaborated example ? Also, update the example with .test.js file
  • Amit Kumar
    Amit Kumar over 3 years
    Please let us know how we will get ...props value ??
  • targumon
    targumon over 2 years
    @taystack you may want to check the answer I posted for this question ^_^
  • targumon
    targumon over 2 years
    @omeralper you may want to check the answer I posted for this question ^_^
  • taystack
    taystack over 2 years
    @targumon I solved this by scoping the jest.fn() outside the mock. It can be referenced easily this way. Mocking the router has been abstracted into a helper method already.
  • targumon
    targumon over 2 years
    Why so cumbersome? This should be written like this: useHistory: () => ({ push: mockPush })