使用 React Testing Library 测试自定义 React Hooks
自定义
React hooks
为开发人员提供了在多个组件之间提取和重用常见功能的能力。然而,测试这些
hooks
可能会有些棘手,特别是对于测试新手来说。在本文中,我们将探讨如何使用
React Testing Library
测试自定义 React hook。
测试 React
组件
首先,让我们回顾一下如何测试一个基本的React
组件。我们来考虑一个名为Counter
的组件的例子,该组件显示一个计数和一个在点击时增加计数的按钮。Counter
组件接受一个可选的prop
,名为initialCount
,如果未提供,则默认为零。以下是代码:
import { useState } from 'react'
type UseCounterProps = {
initialCount?: number
}
export const Counter = ({ initialCount = 0 }: CounterProps = {}) => {
const [count, setCount] = useState(initialCount)
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
要使用 React Testing Library
测试 Counter
组件,我们按照以下步骤进行:
-
- 使用
React Testing Library
的render
函数渲染组件。
- 使用
-
- 使用
React Testing Library
的screen
对象获取DOM
元素。ByRole
是推荐的查询元素的方法。
- 使用
-
- 使用
@testing-library/user-event
库模拟用户事件。
- 使用
-
- 对渲染输出进行断言。
以下测试验证了Counter
组件的功能:
import { render, screen } from '@testing-library/react'
import { Counter } from './Counter'
import user from '@testing-library/user-event'
describe('Counter', () => {
test('renders a count of 0', () => {
render(<Counter />)
const countElement = screen.getByRole('heading')
expect(countElement).toHaveTextContent('0')
})
test('renders a count of 1', () => {
render(<Counter initialCount={1} />)
const countElement = screen.getByRole('heading')
expect(countElement).toHaveTextContent('1')
})
test('renders a count of 1 after clicking the increment button', async () => {
user.setup()
render(<Counter />)
const incrementButton = screen.getByRole('button', { name: 'Increment' })
await user.click(incrementButton)
const countElement = screen.getByRole('heading')
expect(countElement).toHaveTextContent('1')
})
})
第一个测试验证了Counter
组件默认渲染为0
。在第二个测试中,我们为 initialCount prop
传入了值1
,并测试渲染的计数值是否也为1
。
最后,第三个测试检查 Counter
组件在点击增加按钮后是否正确更新了计数。
测试自定义 React hooks
现在,让我们看一个自定义hook
的例子以及如何使用React Testing Library
进行测试。我们已将计数逻辑提取到名为 useCounter
的自定义React hook
中。
该hook
接受一个初始计数作为可选prop
,并返回一个具有当前计数值和增加函数的对象。以下是useCounter hook
的代码:
// useCounter.tsx
import { useState } from "react";
type UseCounterProps = {
initialCount?: number
}
export const useCounter = ({ initialCount = 0 }: CounterProps = {}) => {
const [count, setCount] = useState(initialCount);
const increment = () => {
setCount((prevCount) => prevCount + 1);
};
return { count, increment };
};
使用这个自定义hook
,我们可以很容易地向我们React
应用的任何组件添加计数功能。现在,让我们探讨如何使用 React Testing Library
进行测试。
// useCounter.test.tsx
import { renderHook } from "@testing-library/react";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
test("should render the initial count", () => {
const { result } = renderHook(useCounter);
expect(result.current.count).toBe(0);
});
})
在这个测试中,我们使用renderHook()
渲染我们的useCounter() hook
,并使用 result
对象获取其返回值。然后,我们使用 expect()
验证初始计数是否为 0。
请注意,值保存在result.current
中。将 result
视为最近提交值的引用。
使用 renderHook() 的option
我们还可以通过将选项对象作为 renderHook()
函数的第二个参数传递来测试 hook
是否接受并呈现相同的初始计数:
test("should accept and render the same initial count", () => {
const { result } = renderHook(useCounter, {
initialProps: { initialCount: 10 },
});
expect(result.current.count).toBe(10);
});
在这个测试中,我们使用renderHook()
函数的initialProps
属性将一个initialCount
属性设置为10
的options
对象传递给我们的useCounter()
钩子。然后使用expect()
验证计数是否等于10
。
使用 act() 更新状态
对于我们的最后一个测试,让我们确保增加功能按预期工作。
为了测试 useCounter() hook
的increment
功能是否按预期工作,我们可以使用 renderHook()
渲染 hook
并调用 result.current.increment()
。
然而,当我们运行测试时,它失败并显示错误消息:“Expected count to be 1 but received 0”
。
test("should increment the count", () => {
const { result } = renderHook(useCounter);
result.current.increment();
expect(result.current.count).toBe(1);
});
错误消息还提供了出错的线索:“An update to TestComponent inside a test was not wrapped in act(...).”
。这意味着导致状态更新的代码,在这种情况下是增加函数,应该被包装在 act(...)
中。
在
React Testing Library
中,
act()
辅助函数在确保组件的所有更新被完全处理后再进行断言方面发挥着至关重要的作用。
具体来说,当测试涉及状态更新的代码时,将该代码与 act()
函数一起包装是非常重要的。这有助于准确模拟组件的行为,并确保测试反映真实世界的场景。
请注意,
act()
是React Testing Library
提供的一个辅助函数,用于包装导致状态更新的代码。尽管该库通常会将所有此类代码包装在act()
中,但在测试直接调用导致状态更新的函数的自定义hook
时,这是不可能的。在这种情况下,我们需要手动使用act()
将相关代码包装起来。
// useCounter.test.tsx
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'
test("should increment the count", () => {
const { result } = renderHook(useCounter);
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
通过用 act()
包装increment()
函数,我们确保在执行断言之前应用了对状态的任何修改。这种方法还有助于避免由于异步更新而可能引起的潜在错误。
结论
在使用 React Testing Library
测试自定义 hook
时,我们使用 renderHook()
函数来渲染我们的自定义 hook
,并验证它是否返回了预期的值。如果我们的自定义 hook
接受 props
,我们可以使用renderHook()
函数的initialProps
选项传递它们。
此外,我们必须确保任何导致状态更新的代码都被act()
实用工具函数包装起来,以防止错误发生。
喜欢的朋友记得点赞、收藏、关注哦!!!