【问题标题】:How to test dependency on a React hook如何测试对 React 钩子的依赖
【发布时间】:2021-01-21 03:36:18
【问题描述】:

我正在尝试使用 @testing-library/react-hooks 测试自定义挂钩,但我无法测试依赖项。我们以useEffect 为例:

import { renderHook } from '@testing-library/react-hooks';
import { useEffect } from 'react';

test('test dependency', () => {
  const callback = jest.fn((value: number) => {});
  let currentValue = 5;

  renderHook(() => useEffect(() => callback(currentValue), [currentValue]));
  expect(callback).toBeCalledTimes(1);
  expect(callback).toHaveBeenLastCalledWith(5);

  renderHook(() => useEffect(() => callback(currentValue), [currentValue]));
  expect(callback).toBeCalledTimes(1); // error here: called 2 times in reality
  expect(callback).toHaveBeenLastCalledWith(5);

  currentValue = 6;
  renderHook(() => useEffect(() => callback(currentValue), [currentValue]));
  expect(callback).toBeCalledTimes(2);
  expect(callback).toHaveBeenLastCalledWith(6);
});

预期行为:useEffect 未使用相同的依赖项列表再次调用。

实际行为:useEffect 每次都被调用,可能是因为上下文被销毁并在 renderHook 之间重新创建。

我也尝试将渲染方法放入一个常量中,如下所示:

const myHook = () => useEffect(() => callback(currentValue), [currentValue]);
renderHook(myHook);

但没有运气。有什么方法可以测试依赖列表是否正常工作?

【问题讨论】:

    标签: reactjs unit-testing jestjs react-hooks


    【解决方案1】:

    预计renderHook 在每次调用时都会挂载,相反的做法会阻止它与不相关的钩子一起使用。

    与 React 测试库 render 一样,the result 允许控制渲染实例,这包括 unmountrerender

    currentValue 这样的附加值可以像在组件中一样传递,即 props 对象。

    所以应该是这样的:

      let { rerender, unmount } = renderHook(
        ({ val }) => useEffect(() => callback(val), [val]),
        { initialProps: { val: 5 } }
      );
    
      expect(callback).toBeCalledTimes(1);
      expect(callback).toHaveBeenLastCalledWith(5);
    
      rerender({ val: 5 });
    
      expect(callback).toBeCalledTimes(1);
      expect(callback).toHaveBeenLastCalledWith(5);
    
      rerender({ val: 6 });
    
      expect(callback).toBeCalledTimes(2);
      expect(callback).toHaveBeenLastCalledWith(6);
    

    请注意,代码测试了 React 自己的 useEffect,因此它没有实际用途。

    【讨论】:

    • 非常好,使用{ value: 5 } 对象而不是5 感觉非常hacky,但是这样可以干净利落地完成。当我可以尝试并查看它是否按预期工作时会接受。
    • 您可以坚持使用 currentValue local var 所做的,但对我而言,使用 props 对象感觉非常直观。 renderHook 有效地提供了一个忽略返回布局并专注于单个 hook 的虚拟组件,因此通过 props 与其交换数据是合理的。我没有检查它是如何实现的,但怀疑它是如何实现的,这需要 RTL render 上的一个小包装器。
    【解决方案2】:

    经过一番思考,我想出了以下解决方案:

    import { act, renderHook } from '@testing-library/react-hooks';
    import { useEffect, useState } from 'react';
    
    test('test dependency', () => {
      const callback = jest.fn((value: number) => {});
      const renderCounter = jest.fn();
    
      const { result } = renderHook(() => {
        const [{ value }, setValue] = useState({ value: 5 });
        renderCounter();
        useEffect(() => callback(value), [value]);
        return setValue;
      });
    
      expect(renderCounter).toHaveBeenCalledTimes(1);
      expect(callback).toBeCalledTimes(1);
      expect(callback).toHaveBeenLastCalledWith(5);
    
      act(() => result.current({ value: 5 }));
      expect(renderCounter).toHaveBeenCalledTimes(2);
      expect(callback).toBeCalledTimes(1);
      expect(callback).toHaveBeenLastCalledWith(5);
    
      act(() => result.current({ value: 6 }));
      expect(renderCounter).toHaveBeenCalledTimes(3);
      expect(callback).toBeCalledTimes(2);
      expect(callback).toHaveBeenLastCalledWith(6);
    });
    

    它不是那么漂亮,但它确实有效。 renderCounter 用于确保内部钩子实际上是“重新渲染”。例如,如果您使用5 而不是{value: 5} 作为状态,那么act(() => result.current(5)) 不会“重新渲染”钩子。

    【讨论】:

      猜你喜欢
      • 2019-07-08
      • 1970-01-01
      • 2019-12-28
      • 2020-07-14
      • 2021-05-27
      • 2021-01-28
      • 1970-01-01
      • 2021-11-11
      • 2019-11-11
      相关资源
      最近更新 更多