自定义 React hooks
为开发人员提供了在多个组件之间重用常见功能的能力。然而,测试这些 hooks
可能会有些棘手,特别是对于新手来说。在本博文中,我们将探讨如何使用 React Testing Library
测试自定义 React hook
。
测试 React 组件
首先,让我们回顾一下如何测试一个基本的 React
组件。让我们考虑一个 Counter
组件的示例,该组件显示一个计数和一个按钮,点击按钮时会将计数增加。Counter
组件接受一个名为initialCount
的可选 prop
,如果未提供,则默认为零。以下是代码:
1 | import { useState } from 'react' |
要使用 React Testing Library
测试Counter
组件,我们按照以下步骤进行:
- 使用
React Testing Library
的render
函数渲染组件。
- 使用
- 使用
React Testing Library
的screen
对象获取 DOM 元素。推荐使用ByRole
来查询元素。
- 使用
- 使用
@testing-library/user-event
库模拟用户事件。
- 使用
- 对渲染输出进行断言。
以下测试验证了Counter
组件的功能:
1 | import { render, screen } from '@testing-library/react' |
第一个测试验证了Counter
组件默认情况下以计数 0 渲染。在第二个测试中,我们为initialCount prop
传入了值 1,并测试渲染的计数值是否也为 1。
最后,第三个测试检查了在点击增加按钮后,Counter
组件是否正确更新了计数。
测试自定义 React hooks
现在,让我们看一个自定义hook 的示例,以及如何使用
React Testing Library 对其进行测试。我们将计数逻辑提取到一个名为
useCounter 的自定义
React hook `中。
该 hook
接受一个初始计数作为可选 prop
,并返回一个带有当前计数值和增加函数的对象。以下是useCounter hook
的代码:
1 | // useCounter.tsx |
使用这个自定义 hook
,我们可以轻松地为我们 React
应用程序中的任何组件添加计数功能。现在,让我们探讨如何使用 React Testing Library
对其进行测试。
1 | // useCounter.test.tsx |
测试自定义 React hooks
与测试组件不同。当您尝试通过将其传递给 render()
函数来测试 hook
时,您将收到一个类型错误,指示hook
不能分配给类型 ``ReactElement<any, string | JSXElementConstructor的参数。这是因为自定义
hooks 不像
React `组件一样返回任何 JSX。
另一方面,如果您尝试在没有 render()
函数的情况下调用自定义 hook
,您将在终端中看到一个控制台错误,指示 hooks
只能在函数组件内部调用。
1 | // useCounter.test.tsx |
测试自定义React hooks
确实可能会有些棘手。
使用 renderHook() 测试自定义 hooks
要在 React
中测试自定义 hooks
,我们可以使用 React Testing Library
提供的renderHook()
函数。该函数允许我们渲染一个hook
并访问其返回值。让我们看看如何更新我们的 useCounter() hook
的测试来使用 renderHook()
:
1 | // useCounter.test.tsx |
在这个测试中,我们使用 renderHook()
渲染我们的 useCounter() hook
,并使用result
对象获取其返回值。然后,我们使用 expect()
来验证初始计数是否为 0。
请注意,值存储在
result.current
中。将result
视为对最近提交的值的引用。
使用 renderHook() 的选项
我们还可以通过将选项对象作为 renderHook()
函数的第二个参数传递来测试 hook
是否接受并渲染相同的初始计数:
1 | test("should accept and render the same initial count", () => { |
在这个测试中,我们通过将一个 options
对象作为 renderHook()
函数的 initialProps
选项传递给我们的 useCounter() hook
,将initialCount
属性设置为 10。然后,我们使用 expect() 来验证计数是否等于 10。
使用 act() 来更新状态
对于我们的最后一个测试,让我们确保增量功能按预期工作。
要测试 useCounter() hook
的增量功能是否按预期工作,我们可以使用 renderHook()
渲染 hook 并调用 result.current.increment()
。
然而,当我们运行测试时,它失败,并显示错误消息,“Expected count to be 1 but received 0”
。
1 | test("should increment the count", () => { |
控制台错误:使用计数器应该增加数额。预期为 1,接收到 0
错误消息还提供了关于出错原因的线索:“在测试中,对 TestComponent
的更新未包装在act(...)
中。”这意味着引起状态更新的代码,本例中为增量函数,应该被包装在 act(...)
中。
在 React Testing Library
中,act()
辅助函数在确保组件的所有更新都被完全处理之前进行断言时起着重要作用。
具体来说,当测试涉及状态更新的代码时,将该代码用 act()
函数包装是至关重要的。这有助于准确模拟组件的行为,并确保您的测试反映了真实的场景。
请注意,act()
是由React Testing Library
提供的一个辅助函数,用于包装导致状态更新的代码。虽然库通常会将所有这样的代码包装在 act() 中,但在测试直接调用导致状态更新的函数的自定义 hooks 时,这是不可能的。在这种情况下,我们需要手动使用 act() 包装相关代码。
1 | // useCounter.test.tsx |
通过将 increment()
函数用act()
包装,我们确保状态的任何修改都会在执行断言之前应用。这种方法也有助于避免由于异步更新而可能引发的潜在错误。
结论
在使用 React Testing Library
测试自定义 hooks
时,我们使用 renderHook()
函数渲染我们的自定义 hook
,并验证它返回了预期的值。如果我们的自定义hook
接受 props
,我们可以使用 renderHook()
函数的 initialProps
选项将它们传递进去。
此外,我们必须确保任何导致状态更新的代码都被act()
实用工具函数包装,以防止错误发生。