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

React-Query二进制文件上传Hook单元测试失败问题排查

问题描述

我有一个用于向端点上传二进制文件的React-Query Hook,该Hook功能正常,但对应的单元测试却失败了。报错信息显示waitFor超时1000ms,且为网络错误。

Hook代码

import { useMutation, useQueryClient } from 'react-query';

import { IUploadFieldRequest } from '../constants/types';
import { licenseServiceAPI } from '../services';

export const URI = () => '/licenses/upload';

export const useUploadLicense = () => {
  const queryClient = useQueryClient();
  const [apiService] = licenseServiceAPI<IUploadFieldRequest>({
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });

  const {
    data: response,
    mutateAsync,
    isLoading,
    isSuccess
  } = useMutation(
    (formData: IUploadFieldRequest) => {
      const form = new FormData();
      Array.from(formData.files)?.forEach((file) => {
        form.append('license-file', file);
      });

      return apiService.post(URI(), form);
    },
    {
      onError: () => {
        return queryClient.invalidateQueries('licenseUpload');
      }
    }
  );

  return {
    data: response?.data,
    uploadFiles: mutateAsync,
    isLoading,
    isSuccess
  };
};

export default useUploadLicense;

测试代码

import { act, renderHook } from '@testing-library/react-hooks';

import { queryClientInstance } from '@oam/shared/test-utils';

import useUploadLicense from './use-upload-license';

const file = new File(['file'], 'license-file.json', { type: 'multipart/form-data' });

it('upload files', async () => {
  const { result, waitFor } = renderHook(() => useUploadLicense(), {
    wrapper: queryClientInstance
  });

  act(() => {
    result.current.uploadFiles({ files: [file] });
  });
  await waitFor(() => expect(result.current.isLoading).toBeTruthy());
  await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
});

接口定义

type IUploadFieldRequest = {
  files: File[];
};

服务代码

import { AxiosRequestConfig } from 'axios';

import { requestConfig } from '@oam/shared/services';
import init, { Response } from '@oam/shared/utils/request-wrapper';

export const BASE_PATH =
  process.env.NODE_ENV === 'development'
    ? '/myurl/v1'
    : 'http://localhost:7999/v1';

export const licenseServiceAPI = <TRequest, TResponse = TRequest>(
  config?: AxiosRequestConfig
): [Response<TRequest, TResponse>, AxiosRequestConfig] => {
  const newConfig = requestConfig({
    baseURL: BASE_PATH,
    headers: {
      Accept: 'application/json',
      ...config?.headers
    },
    responseType: config?.responseType ? config?.responseType : undefined
  });
  return [init(newConfig), newConfig];
};

export default licenseServiceAPI;
解决办法

1. Mock API请求(核心解决网络错误)

单元测试不能发送真实网络请求,必须mock掉licenseServiceAPI的post方法,让它返回模拟的成功响应:

在测试文件顶部添加mock代码:

// Mock 整个license服务模块
jest.mock('../services', () => ({
  licenseServiceAPI: jest.fn(() => [
    {
      // 模拟post方法返回成功Promise
      post: jest.fn(() => Promise.resolve({ data: '上传成功' }))
    },
    {}
  ])
}));

2. 修正测试中的异步逻辑

uploadFiles是异步函数,不能用act同步包裹调用,正确做法是直接await该方法,或结合waitFor跟踪状态:

修改后的测试用例:

it('upload files', async () => {
  const { result, waitFor } = renderHook(() => useUploadLicense(), {
    wrapper: queryClientInstance
  });

  // 触发上传并等待异步操作启动
  const uploadPromise = result.current.uploadFiles({ files: [file] });

  // 验证加载状态
  await waitFor(() => expect(result.current.isLoading).toBe(true));

  // 等待上传完成
  await uploadPromise;

  // 验证成功状态和返回数据
  await waitFor(() => {
    expect(result.current.isSuccess).toBe(true);
    expect(result.current.data).toBe('上传成功');
  });
});

3. 调整waitFor超时时间(可选)

如果mock后的异步操作仍有超时风险,可以给waitFor指定更长的超时时间:

await waitFor(() => expect(result.current.isSuccess).toBe(true), { timeout: 3000 });

4. 检查测试环境的BASE_PATH

确保测试时BASE_PATH不会指向真实服务器,可在测试前覆盖环境变量:

beforeAll(() => {
  process.env.NODE_ENV = 'test';
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 13:35:33