使用renderHook测试useFetch自定义Hook时无法获取API返回数据
如何正确测试useFetch自定义Hook?
你编写了useFetch自定义Hook用于获取产品数据,在测试时使用renderHook始终拿到空的products数组,即使尝试waitFor也无法获取API返回结果。问题的核心在于测试时直接依赖真实API会导致异步状态未更新就断言,且真实接口不稳定,必须通过mock fetch配合异步测试工具来解决。
解决方案:Mock Fetch + 异步状态等待
1. Mock全局Fetch接口
在测试中模拟fetch的返回值,避免调用真实API,确保测试稳定且快速。以下是完整的测试代码示例:
import { renderHook, waitFor } from "@testing-library/react"; import { expect, vi } from "vitest"; import { useFetch } from "../../hooks/useFetch"; // Mock全局fetch方法,模拟API返回数据 vi.spyOn(global, 'fetch').mockResolvedValue({ json: async () => ({ products: Array(30).fill({ id: 1, title: "测试产品" }), total: 100 }) }); describe("Products组件", () => { // 测试初始加载30条产品 test("正确渲染第一页的30个产品", async () => { const { result } = renderHook(() => useFetch()); // 等待products状态更新完成 await waitFor(() => { expect(result.current.products.length).toBe(30); }); // 验证fetch是否被正确调用 expect(fetch).toHaveBeenCalledWith( "https://dummyjson.com/products?skip=0&limit=30" ); }); // 测试翻页功能 test("触发下一页时获取正确的数据", async () => { const { result } = renderHook(() => useFetch()); // 调用翻页方法到第2页 result.current.handleNextPage(2); await waitFor(() => { expect(result.current.products.length).toBe(30); }); // 验证fetch的参数是否符合分页逻辑 expect(fetch).toHaveBeenCalledWith( "https://dummyjson.com/products?skip=30&limit=30" ); }); // 测试结束后恢复原始fetch实现,避免影响其他测试 afterEach(() => { global.fetch.mockRestore(); }); });
2. 关键注意点
- 必须使用
async/await+waitFor:Hook内部的useEffect会触发异步fetch,初始状态是空数组,必须等待状态更新后再断言。 - Mock fetch是必要的:真实API依赖外部服务,测试时可能因网络、服务状态导致失败,mock后测试完全可控。
- 验证fetch调用参数:确保Hook的分页逻辑正确,比如第2页时
skip参数应为30((2-1)*30)。
为什么之前的waitFor没生效?
大概率是没有mock fetch,测试环境中真实的fetch请求被阻止或超时,导致products状态永远无法更新。mock fetch后,请求会立即返回模拟数据,waitFor就能捕获到状态变化。
内容的提问来源于stack exchange,提问作者Matheus Oliveira Damião
相关产品推荐
相关产品推荐

