如何正确测试React中的自定义Fetch Hook?
问题分析与解决方案
你的测试无法获取fetch回调后的状态,核心问题有两个:
- 测试代码未接收
renderHook的返回结果,result变量未定义 - 没有mock异步的
fetchList函数,真实请求在测试环境中无法正常完成,导致状态停留在初始阶段
另外你的自定义Hook存在小瑕疵:catch块仅设置了error,未将status更新为error,这会导致请求失败时状态一直停留在pending,测试大概率会超时。
步骤1:优化自定义Hook(推荐)
先补全错误状态的更新,同时遵循Hook命名规范(以use开头):
const useFetchList = () => { const [list, setList] = useState(null); const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); setStatus('pending'); fetchList(controller) .then((data) => { setList(data); setStatus('success'); }) .catch(error => { setError(error); setStatus('error'); // 新增:失败时同步更新状态 }); return () => controller.abort(); }, []); return { list, status, error }; };
步骤2:编写正确的测试代码
场景1:fetchList是外部导出的函数
用Jest mock该异步函数,同时正确使用renderHook和waitFor:
import { renderHook, waitFor } from "@testing-library/react"; import useFetchList from "./useFetchList"; import fetchList from "./path-to-fetchList"; // mock外部fetchList函数 jest.mock("./path-to-fetchList", () => ({ __esModule: true, default: jest.fn() })); it('should return success status with data', async () => { // 定义模拟返回数据 const mockData = [{ id: 1, name: 'test' }]; // 让mock函数返回成功Promise fetchList.mockResolvedValue(mockData); // 渲染Hook并接收result const { result } = renderHook(() => useFetchList()); // 初始状态应为pending expect(result.current.status).toBe('pending'); // 等待状态更新为success await waitFor(() => { expect(result.current.status).toBe('success'); expect(result.current.list).toEqual(mockData); expect(result.current.error).toBeNull(); }); }); it('should return error status when fetch fails', async () => { const mockError = new Error('Fetch failed'); fetchList.mockRejectedValue(mockError); const { result } = renderHook(() => useFetchList()); expect(result.current.status).toBe('pending'); await waitFor(() => { expect(result.current.status).toBe('error'); expect(result.current.error).toEqual(mockError); expect(result.current.list).toBeNull(); }); });
场景2:fetchList是Hook内部函数
建议将其改为可传入的参数,方便测试时替换:
// 修改Hook,允许自定义fetch函数 const useFetchList = (fetchFn = fetchList) => { const [list, setList] = useState(null); const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); setStatus('pending'); fetchFn(controller) .then((data) => { setList(data); setStatus('success'); }) .catch(error => { setError(error); setStatus('error'); }); return () => controller.abort(); }, [fetchFn]); // 添加依赖项 return { list, status, error }; };
对应测试无需jest.mock,直接传入模拟函数:
it('should return success status with data', async () => { const mockData = [{ id: 1, name: 'test' }]; const mockFetchFn = jest.fn().mockResolvedValue(mockData); const { result } = renderHook(() => useFetchList(mockFetchFn)); await waitFor(() => { expect(result.current.status).toBe('success'); expect(mockFetchFn).toHaveBeenCalled(); }); });
内容的提问来源于stack exchange,提问作者Fram Zenith
相关产品推荐
相关产品推荐

