React Hooks 测试指南:testing-library/react-hooks-testing-library 基础用法
前言
在现代 React 开发中,Hooks 已经成为组件逻辑复用的主要方式。然而,如何有效地测试这些 Hooks 却是一个值得探讨的话题。本文将深入介绍如何使用 testing-library/react-hooks-testing-library 来测试基础 Hooks。
基础 Hook 测试
1. 渲染 Hook
让我们从一个简单的计数器 Hook 开始:
import { useState, useCallback } from 'react'
export default function useCounter() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }
}
测试这个 Hook 的基本用法如下:
import { renderHook } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('测试计数器初始状态', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
expect(typeof result.current.increment).toBe('function')
})
这里有几个关键点需要注意:
renderHook
是测试库提供的核心方法result.current
包含了 Hook 返回的最新值- 测试结构与 Hook 的实际使用方式非常相似
2. 测试状态更新
仅仅测试初始状态是不够的,我们还需要验证 Hook 的行为:
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('测试计数器递增功能', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
重要提示:
- 所有可能触发状态更新的操作都必须包裹在
act
中 - 这是为了模拟 React 在浏览器中的实际行为
- 忘记使用
act
会导致测试警告甚至失败
处理 Hook 参数
1. 初始值参数
许多 Hook 会接受参数来定制其行为。例如,我们的计数器可以接受初始值:
import { useState, useCallback } from 'react'
export default function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }
}
测试带参数的 Hook:
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('测试自定义初始值的计数器', () => {
const { result } = renderHook(() => useCounter(9000))
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(9001)
})
2. 动态更新参数
当 Hook 依赖的参数可能变化时,我们需要测试这种场景:
import { useState, useCallback } from 'react'
export default function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = useCallback(() => setCount((x) => x + 1), [])
const reset = useCallback(() => setCount(initialValue), [initialValue])
return { count, increment, reset }
}
测试参数更新的方法有两种:
方法一:使用变量重新渲染
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('测试重置功能响应初始值变化', () => {
let initialValue = 0
const { result, rerender } = renderHook(() => useCounter(initialValue))
initialValue = 10
rerender()
act(() => {
result.current.reset()
})
expect(result.current.count).toBe(10)
})
方法二:使用 initialProps 和 rerender
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('测试重置功能响应初始值变化', () => {
const { result, rerender } = renderHook(({ initialValue }) => useCounter(initialValue), {
initialProps: { initialValue: 0 }
})
rerender({ initialValue: 10 })
act(() => {
result.current.reset()
})
expect(result.current.count).toBe(10)
})
两种方法的比较:
- 第一种方法更直接,适合简单场景
- 第二种方法更结构化,适合复杂参数或多个参数的情况
- 第二种方法还能避免变量作用域问题(见下文)
处理副作用清理
当测试涉及副作用的 Hook 时,正确的清理方式尤为重要:
import { useEffect } from 'react'
import { renderHook } from '@testing-library/react-hooks'
import sideEffect from './sideEffect'
test('测试副作用清理', () => {
const { rerender } = renderHook(
({ id }) => {
useEffect(() => {
sideEffect.start(id)
return () => {
sideEffect.stop(id)
}
}, [id])
},
{
initialProps: { id: 'first' }
}
)
rerender({ id: 'second' })
expect(sideEffect.get('first')).toBe(false)
expect(sideEffect.get('second')).toBe(true)
})
关键点:
- 使用
initialProps
可以确保清理函数使用正确的值 - 这种方法避免了变量作用域带来的潜在问题
- 特别适合测试
useEffect
的清理函数
最佳实践总结
- 始终使用 act:任何可能触发状态更新的操作都必须包裹在
act
中 - 避免直接解构 result.current:这会锁定当前值,导致无法获取更新后的状态
- 选择适合的参数传递方式:根据测试复杂度选择变量或
initialProps
方式 - 特别注意副作用清理:使用
initialProps
可以避免作用域问题 - 保持测试简洁:每个测试应该专注于验证 Hook 的一个特定行为
通过遵循这些原则,你可以构建出可靠、可维护的 Hook 测试套件,确保你的自定义 Hook 在各种场景下都能按预期工作。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考