如何在玩笑中模拟 useHistory 钩子?

IT技术 reactjs typescript react-router jestjs enzyme
2021-04-12 19:07:45

我在带有typescript的 react router v5.1.2 中使用 UseHistory 钩子吗?运行单元测试时,我遇到了问题。

类型错误:无法读取未定义的属性“历史”。

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() }));但它仍然不起作用。

6个回答

在浅化使用useHistory.

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

jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    push: jest.fn(),
  }),
}));
对于那些使用 TypeScript 的人,如果组件同时使用Link,这种方法可能会导致“React.createElement: type is invalid — expected a string”错误useHistoryErhan的方法不会导致这个问题。
2021-05-24 19:07:45
但是你如何监视 useHistory 函数呢?
2021-05-26 19:07:45
使用 TypeScript 的可以参考这个:stackoverflow.com/q/62774929/10959940 :)
2021-05-27 19:07:45
有没有办法捕获useHistory().push()调用?
2021-06-01 19:07:45
@proustibat,你能提供一个详细的例子吗?此外,使用 .test.js 文件更新示例
2021-06-10 19:07:45

这个对我有用:

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: jest.fn()
  })
}));
这在 TypeScript 中不起作用,因为它会出现以下错误: TS2698: Spread types may only be created from object types.
2021-05-28 19:07:45
@Erhan 我也做了同样的事情。但它再次抛出错误:TypeError:无法读取未定义的属性“历史”。有什么建议吗?
2021-05-29 19:07:45
这种方法保留了您可能不想模拟的其他 react-router-dom 功能
2021-06-21 19:07:45
有关 TypeScript 支持,请参阅此答案
2021-06-21 19:07:45

这是一个更详细的例子,取自工作测试代码(因为我很难实现上面的代码):

组件.js

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

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

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

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

不是useHistory你想嘲笑。相反,您只想为它提供您控制的历史对象。

这也允许您检查push调用,就像 2 个最重要的答案一样(在撰写本文时)。

如果确实如此,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()
})
我试过这个,我的 onClick 正在工作(通过控制台日志检查)但是 history.push 没有被触发,你能帮忙调试它吗
2021-05-30 19:07:45
@RanjanKumar 很难在没有看到代码的情况下进行调试……但我还是会尝试:您是否像在我的示例中那样监视“推送”,但 toHaveBeenCalled 期望失败了?
2021-06-17 19:07:45

在 github react-router repo 中,我发现 useHistory 钩子使用单例上下文,当我开始在挂载 MemoryRouter 中使用时,它找到了上下文并开始工作。所以修复它

import { MemoryRouter } from 'react-router-dom';
const tree =  mount(<MemoryRouter><QuestionContainer {...props} /> </MemoryRouter>);
请让我们知道我们将如何获得...props值?
2021-05-28 19:07:45