如何在要测试的 React 组件中模拟自定义挂钩?

How to mock a custom hook inside of a React component you want to test?

如果您有一个调用自定义挂钩来获取数据的 React 组件,那么在测试 React 组件时模拟该内部自定义挂钩结果的最佳方法是什么?我看到 2 种主要方法:

1) Jest.mock 自定义挂钩。这似乎是最推荐的方法,但它似乎需要测试对内部实现细节以及它可能需要模拟的内容有更多的了解,而不是组件的 props 接口可能建议的内容(假设使用 prop-types 或打字稿)

2) 使用依赖注入方法。将钩子声明为道具,但将其默认为真正的钩子,这样您就不必在渲染组件的任何地方都设置它,但允许用模拟覆盖以进行测试。这是一个人为设计的 codesandbox 示例,其中包含模拟自定义挂钩的测试:

https://codesandbox.io/s/dependency-inject-custom-hook-for-testing-mjqlf?fontsize=14&module=%2Fsrc%2FApp.js

2 需要更多的输入,但似乎更容易用于测试。但是,测试必须了解组件的内部实现细节才能测试渲染输出的任何条件逻辑,所以这可能并不重要,1 是最好的方法。 1是要走的路吗?你看到什么权衡?我是否完全错过了另一种方法?

使用 mocking hook 本身,你永远不知道真正的 hook 是否与你的组件一起工作得很好。

以传递钩子为道具,很难让钩子相互通信。例如。当您需要自定义挂钩从同一组件 useState 调用 setter 时。您将需要使用越来越多的参数来扩展自定义挂钩。

  1. 您可以模拟外部 API 调用 - 我的意思是模拟 fetchXHR。它仍然需要知道一些实现细节 - 事实上你是 运行 HTTP 请求 - 但你的测试应该知道的事情更少。

为什么不模拟进行 api 调用的底层方法呢?

例如,如果您使用 fetch() 检索数据,则您可以模拟它。这样您就可以为该调用定义自定义响应,这将使测试挂钩本身变得容易。

这个问题已经有几个月了,但如果您还没有找到好的解决方案,我写了一个包可能会有帮助。我经历了类似的思考过程,包括 "what if I inject the hooks into the component?" 事情变得很奇怪。

我基本上想要一个连接器来避免为演示组件使用额外的包装器来测试它们。

我想出了 react-hooks-compose,它可以让您将挂钩和演示者分开,并单独或一起测试它们:https://www.npmjs.com/package/react-hooks-compose

export const useFetch = () => {
  const [user, setUser] = useState();

  useEffect(() => {
    fetchData('some-url') // <-- Fetches data on mount
      .then(res => setUser(res.data));
  }, []);

  return {user};
}

// composeHooks passes the values from your hooks as props
export const UserPresenter = ({user}) => {
  return <div>You fetched data for: {user.name}</div>;
}

export default composeHooks({ useFetch })(DataPresenter);

现在你不需要模拟钩子了,你可以用一个道具来测试演示者:

it('presents user', () => {
  const { queryByText } = render(<UserPresenter user={{name: 'Mary'}} />); // <-- Named export
  expect(queryByText('Mary')).toBeTruthy();
});

或者,您可以选择更高级别的集成测试:

it('fetches data', () => {
  fetchData.mockResolvedValue('Mary');
  const { queryByText } = render(<UserWithData />); // <-- Default export
  expect(queryByText('Mary')).toBeFalsy();
  return wait(() => {
    expect(queryByText('Mary')).toBeTruthy();
  });
});

如果愿意,您甚至可以对 hook 进行单元测试。

使用 jest 模拟您的自定义挂钩。

import * as useCustomHook from '../hooks/useCustomHooks'

const spy = jest.spyOn(useCustomHook, 'default')
spy.mockReturnValue({
name: 'test'
})