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

React TypeScript自定义Hook测试求助:useFetch测试实现

测试自定义useFetch Hook的正确方法

问题背景

我正在学习React测试,需要测试一个自定义的useFetch Hook,但完全不知道该怎么操作。

自定义useFetch Hook代码

import { useState, useCallback } from 'react';
import axios from 'axios';

export const useFetch = () => {
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchData = useCallback(async (url: string) => {
    setLoading(true);
    try {
      const response = await axios.get(url);
      const data = await response.data.data;
      setLoading(false);
      return data;
    } catch (error: any) {
      if (error.name !== 'AbortError') {
        setLoading(false);
        setError(error.message);
      }
    }
  }, []);

  return { error, loading, fetchData };
};

我尝试的测试代码(无效)

import { useFetch } from '../../hooks/useFetch';
import mockAxios from 'jest-mock-axios';
import axios from 'axios';
import { renderHook } from '@testing-library/react-hooks';

jest.mock('axios');

describe('useFetch', () => {
  afterEach(() => {
    mockAxios.reset();
  });
  it('fetches successfully data from an API', async () => {
    const { result,waitFor  } = renderHook(() => useFetch());
    let responseObj = { data: 'test response' };
    
    mockAxios.mockResponseFor({ url: '/get' }, responseObj);
    expect(result.current.error).toBe(null);
    await expect(result.current.fetchData('react')).resolves.toBe(responseObj);
  });
});

修正后的测试方案

你的测试代码存在几处问题:混用了jest.mock('axios')和第三方mockAxios库、Mock响应结构与Hook中response.data.data的取值逻辑不匹配、未正确处理状态更新后的异步断言。以下是正确的测试实现及说明:

1. 依赖准备

确保已安装必要测试依赖:

npm install --save-dev @testing-library/react-hooks jest axios @types/jest @types/react

2. 完整测试代码

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

// 用Jest直接Mock axios,无需额外库
jest.mock('axios');
// TypeScript类型断言,识别Mock后的axios实例
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('useFetch Hook', () => {
  // 每个测试用例执行后重置Mock
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should set loading to true, then false, and return data on successful fetch', async () => {
    // 定义匹配Hook取值逻辑的Mock响应
    const mockData = 'test data';
    mockedAxios.get.mockResolvedValue({
      data: {
        data: mockData
      }
    });

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

    // 初始状态断言
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBe(null);

    // 触发请求
    const fetchPromise = result.current.fetchData('https://example.com/api');

    // 调用后立即检查loading状态
    expect(result.current.loading).toBe(true);

    // 等待状态更新完成,断言最终结果
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    const fetchedData = await fetchPromise;
    expect(fetchedData).toBe(mockData);
    expect(result.current.error).toBe(null);
    // 验证axios.get的调用参数
    expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/api');
  });

  it('should set loading to false and error message on fetch failure (non-AbortError)', async () => {
    const mockError = new Error('Network Error');
    mockedAxios.get.mockRejectedValue(mockError);

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

    // 触发请求
    await result.current.fetchData('https://example.com/api');

    // 断言错误状态
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.error).toBe('Network Error');
    });
    expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/api');
  });

  it('should not set error when error is AbortError', async () => {
    const abortError = new Error('Aborted');
    abortError.name = 'AbortError';
    mockedAxios.get.mockRejectedValue(abortError);

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

    await result.current.fetchData('https://example.com/api');

    // 断言loading重置但error保持null
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.error).toBe(null);
    });
  });
});

关键修正说明

  • 统一Mock方式:直接使用Jest原生的jest.mock('axios')进行Mock,避免与第三方Mock库冲突。
  • 匹配响应结构:Hook中通过response.data.data获取数据,所以Mock响应必须嵌套data字段,确保取值逻辑一致。
  • 异步状态断言:使用waitFor等待Hook的状态更新完成后再执行断言,因为React状态更新是异步的。
  • 覆盖所有分支:测试了成功请求、普通错误、AbortError三种场景,覆盖Hook的全部逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 18:20:42