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

自定义useFetch Hook单元测试出现Object.dispatchError问题求助

自定义useFetch Hook单元测试报错Object.dispatchError的问题

问题背景

运行自定义useFetch Hook的单元测试时,遇到Object.dispatchError错误,相关代码及报错信息如下:

Hook代码

import { useState, useEffect } from 'react';

const useFetch = (url) => {

  const [data, setData] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);

    const apiCall = async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(`Error - ${res.status}`);
        const json = await res.json();

        setData(json);
        setError(null);
      } catch (err) {
        setError(`useFetch Error!`);
      } finally {
        setLoading(false);
      }
    };

    apiCall();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;

单元测试代码

import { renderHook, waitFor } from "@testing-library/react";
import useFetch from './useFetch';

it('Should handle no URL provided', async () => {
  const { result } = renderHook(() => useFetch());

  await waitFor(() => {
    expect(result.current.error).toBe("useFetch Error!");
  });
});

报错信息

console.error
Error: AggregateError
  at Object.dispatchError (jsdom\living\xhr\xhr-utils.js:63:19)
  at Request.<anonymous> (jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:655:18)
  at Request.emit (node:events:531:35)
  at ClientRequest.<anonymous> (jsdom\lib\jsdom\living\helpers\http-request.js:121:14)
  at ClientRequest.emit (node:events:519:28)
  at Socket.socketErrorListener (node:_http_client:492:9)
  at Socket.emit (node:events:519:28)
  at emitErrorNT (node:internal/streams/destroy:169:8)
  at emitErrorCloseNT (node:internal/streams/destroy:128:3)
  at processTicksAndRejections (node:internal/process/task_queues:82:21) undefined

怀疑单元测试中waitFor写法存在问题,想请教如何正确对该自定义Hook进行单元测试?


解决方案

1. 错误根源分析

报错核心并非waitFor写法问题,而是未传入URL时,fetch调用无效地址触发底层网络错误,jsdom无法处理这类无效请求,抛出AggregateError。单元测试的核心原则是隔离外部依赖,不能依赖真实网络请求,必须mockfetch函数。

2. 正确的单元测试写法

通过mock全局fetch函数,模拟不同请求场景,验证Hook的行为:

修改后的错误分支测试代码

import { renderHook, waitFor } from "@testing-library/react";
import useFetch from './useFetch';

// Mock全局fetch函数
global.fetch = jest.fn();

it('Should handle no URL provided', async () => {
  // 模拟fetch调用失败
  fetch.mockRejectedValueOnce(new Error('Invalid URL'));

  const { result } = renderHook(() => useFetch());

  await waitFor(() => {
    expect(result.current.error).toBe("useFetch Error!");
    expect(result.current.loading).toBe(false);
  });

  // 验证fetch被调用一次
  expect(fetch).toHaveBeenCalledTimes(1);
});

3. Hook健壮性优化

为避免空URL触发无效请求,可在Hook内提前做参数校验:

const useFetch = (url) => {
  const [data, setData] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // 新增URL校验,提前终止逻辑
    if (!url) {
      setError(`useFetch Error!`);
      setLoading(false);
      return;
    }

    setLoading(true);

    const apiCall = async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(`Error - ${res.status}`);
        const json = await res.json();

        setData(json);
        setError(null);
      } catch (err) {
        setError(`useFetch Error!`);
      } finally {
        setLoading(false);
      }
    };

    apiCall();
  }, [url]);

  return { data, loading, error };
};

对应的空URL分支测试可简化为:

it('Should handle empty URL immediately', () => {
  const { result } = renderHook(() => useFetch());

  // 无需waitFor,直接断言
  expect(result.current.error).toBe("useFetch Error!");
  expect(result.current.loading).toBe(false);
  // 验证fetch未被调用
  expect(fetch).not.toHaveBeenCalled();
});

4. 其他测试场景补充

除错误分支,还需覆盖成功加载、加载中、HTTP错误状态等场景:

// 测试成功加载
it('Should fetch data successfully', async () => {
  const mockData = { id: 1, name: 'Test Data' };
  fetch.mockResolvedValueOnce({
    ok: true,
    json: async () => mockData,
  });

  const { result } = renderHook(() => useFetch('https://example.com/api'));

  // 校验加载中状态
  expect(result.current.loading).toBe(true);

  await waitFor(() => {
    expect(result.current.loading).toBe(false);
    expect(result.current.data).toEqual(mockData);
    expect(result.current.error).toBe(null);
  });

  expect(fetch).toHaveBeenCalledWith('https://example.com/api');
});

// 测试HTTP错误状态
it('Should handle HTTP error status', async () => {
  fetch.mockResolvedValueOnce({
    ok: false,
    status: 404,
  });

  const { result } = renderHook(() => useFetch('https://example.com/api'));

  await waitFor(() => {
    expect(result.current.error).toBe("useFetch Error!");
    expect(result.current.loading).toBe(false);
  });
});

内容的提问来源于stack exchange,提问作者Scottish Smile

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:31:17