【问题标题】:How to mock a custom hook inside of a React component you want to test?如何在要测试的 React 组件中模拟自定义钩子?
【发布时间】:2019-06-07 15:45:44
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我一直在想同样的事情。在引入 React Hooks 之前,我使用 recompose 大约一年,并且喜欢它为解耦和简单的单元测试所做的事情。 Andrew Clark (recompose) 也在 React Hooks 上工作,并建议在 recompose 上使用它,但我还没有看到很好的例子来说明同样的解耦能力。有没有人有任何关于如何以与重构方法 HoC 相同的方式处理钩子的体面示例?
  • 这么多@WillSchoenberger!

标签: reactjs unit-testing react-hooks


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    这个问题已经有几个月的历史了,但是如果您还没有找到好的解决方案,我写了一个可能会有所帮助的包。我经历了类似的思考过程,包括“如果我将钩子注入到组件中会怎样?”事情变得很奇怪。

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

    我想出了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();
      });
    });
    

    如果你愿意,你甚至可以对钩子进行单元测试。

    【讨论】:

    • 是的,不久前遇到了那个包,忘记更新这个问题。谢谢!
    • 很高兴听到这个消息!
    【解决方案3】:

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

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

    【讨论】:

    • 这样做不再是单元测试。您的组件不应该关心如何检索数据的实现细节;这就是钩子函数的工作。如果您稍后决定更改钩子的实现,则必须重构所有使用它的测试。这也可能失控。想象一下,如果钩子从另一个从useContext 读取的钩子中获取它传递给 api 的数据。
    【解决方案4】:

    有了模拟钩子本身,你永远不知道真实的钩子是否与你的组件一起工作得很好。

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

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

    【讨论】:

    • "将 hook 作为道具传递,让 hooks 相互通信真的很困难。"这与从模块中导入钩子有什么不同?如果您导入,您仍然不能使用 .e.g useState setter,可以吗?除非你将它作为参数传递给钩子。如果你注入钩子,你也可以这样做
    猜你喜欢
    • 2021-04-17
    • 1970-01-01
    • 2021-03-10
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 2021-04-25
    • 2020-07-10
    • 2020-08-15
    相关资源
    最近更新 更多