测试含fetch请求的React自定义Hook:状态未更新问题解决
React自定义Hook测试问题解决
问题描述
我编写了一个简单的React自定义Hook,通过fetch调用API并将数据存入状态,返回该状态供组件使用。
useTodos.js代码:
import { useEffect, useState } from "react"; export function useTodos() { const [todos, setTodos] = useState([{ a: 1 }]); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/todos?_limit=1") .then((response) => response.json()) .then((data) => setTodos(data)); }, []); return todos; }
useTodos.test.js代码:
import { waitFor, renderHook } from "@testing-library/react"; import { useTodos } from "./useTodos"; test("fetch should return data", async () => { const data = renderHook(() => useTodos()); console.log(data.result.current); await waitFor(() => { console.log(data.result.current); }); });
实际输出:
console.log [ { a: 1 } ] at Object.<anonymous> (src/useTodos.test.js:6:11) console.log [ { a: 1 } ] at src/useTodos.test.js:8:13
原本预期waitFor回调中的状态会是API返回的结果,但实际始终是useState设置的初始状态[{ a: 1 }],需要修正这个问题。
解决方法
1. 模拟全局fetch函数
测试场景下不应调用真实外部API,既影响测试速度也可能导致不稳定。我们需要模拟fetch的返回值:
// 在测试文件开头添加Mock逻辑 global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve([{ id: 1, title: "Test Todo", completed: false }]), }) );
2. 在waitFor中添加断言逻辑
waitFor通过断言失败来触发重试等待,单纯打印无法让它感知状态变化。必须添加断言来检测状态更新:
test("fetch should return data", async () => { const { result } = renderHook(() => useTodos()); // 验证初始状态 expect(result.current).toEqual([{ a: 1 }]); // 等待状态更新并验证API返回数据 await waitFor(() => { expect(result.current).toEqual([{ id: 1, title: "Test Todo", completed: false }]); }); });
3. 可选:测试后清除Mock
避免Mock影响其他测试用例:
afterEach(() => { jest.restoreAllMocks(); });
完整测试代码示例
import { waitFor, renderHook } from "@testing-library/react"; import { useTodos } from "./useTodos"; // Mock全局fetch global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve([{ id: 1, title: "Test Todo", completed: false }]), }) ); test("fetch should return data", async () => { const { result } = renderHook(() => useTodos()); // 初始状态验证 expect(result.current).toEqual([{ a: 1 }]); // 等待API请求完成,验证状态更新 await waitFor(() => { expect(result.current).toEqual([{ id: 1, title: "Test Todo", completed: false }]); }); // 额外验证fetch是否调用了正确的URL expect(fetch).toHaveBeenCalledWith("https://jsonplaceholder.typicode.com/todos?_limit=1"); }); // 测试后清除Mock afterEach(() => { jest.restoreAllMocks(); });
原代码问题分析
- 未Mock
fetch:测试环境中fetch可能未被正确实现,导致真实API请求无法正常完成,状态始终未更新。 waitFor无断言:waitFor依赖断言失败来持续等待状态变化,单纯打印操作不会触发重试逻辑,因此直接输出初始状态。
内容的提问来源于stack exchange,提问作者Gautham Muralidharan
相关产品推荐
相关产品推荐

