如何嘲笑useHistory挂钩?

时间:2019-10-15 10:44:15

标签: reactjs typescript react-router jestjs enzyme

我正在使用带有打字稿的React Router v5.1.2使用UseHistory挂钩吗?运行单元测试时,我遇到了问题。

  

TypeError:无法读取未定义的属性“历史”。

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();
    });
});

我也尝试使用jest.mock('react-router', () =>({ useHistory: jest.fn() }));,但仍然无法正常工作。

7 个答案:

答案 0 :(得分:3)

在浅化使用useHistory的React功能组件时,我需要相同的内容。

在我的测试文件中解决了以下模拟问题:

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

答案 1 :(得分:1)

这个对我有用:

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

答案 2 :(得分:1)

戴上我的政治家帽子,我敢说你问错了问题。

您不想嘲笑useHistory。取而代之的是,您只希望将其与您控制的历史对象一起提供。

这也使您可以检查push调用,就像两个最重要的答案一样(撰写本文时)。

如果确实是这样,createMemoryHistory可以帮助您

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()
})

答案 3 :(得分:1)

一种模拟 useHistory 推送功能的方法:

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

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

然后,如何检查函数是否被调用:

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

答案 4 :(得分:0)

在github react-router回购中,我发现useHistory钩子使用单例上下文,当我开始在Mount MemoryRouter中使用它时,它找到了上下文并开始工作。 所以修好它 import { MemoryRouter } from 'react-router-dom'; const tree = mount(<MemoryRouter><QuestionContainer {...props} /> </MemoryRouter>);

答案 5 :(得分:0)

这是一个更详细的示例,摘自有效的测试代码(因为我很难实现上面的代码):

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');
  });

答案 6 :(得分:0)

我发现上述答案非常有帮助。但是,我错过了监视和实际测试功能的能力。但是简单地命名模拟函数首先为我解决了这个问题。

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