You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 07:32:44