如何测试对 React 钩子的依赖
Posted
技术标签:
【中文标题】如何测试对 React 钩子的依赖【英文标题】:How to test dependency on a React hook 【发布时间】: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);
但没有运气。有什么方法可以测试依赖列表是否正常工作?
【问题讨论】:
【参考方案1】:预计renderHook
在每次调用时都会挂载,相反的做法会阻止它与不相关的钩子一起使用。
与 React 测试库 render
一样,the result 允许控制渲染实例,这包括 unmount
和 rerender
。
像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))
不会“重新渲染”钩子。
【讨论】:
以上是关于如何测试对 React 钩子的依赖的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 React 钩子处理/链接依赖于另一个的同步副作用