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

JS/TS中Jest无法mock自定义Axios类导致单元测试超时

Jest单测Mock单例模式Axios封装类解决方案

测试超时的核心原因是mock逻辑没有命中实际调用的ApiClient单例,请求走了真实网络逻辑。以下是两种可直接落地的实现方案,优先选第一种。


方案1:直接Mock自定义ApiClient模块(推荐)

业务代码中所有请求最终都通过ApiClient.getInstance()拿到的实例发送,直接mock整个ApiClient模块,完全绕开真实实例初始化、拦截器、重试逻辑,不会触发额外依赖调用,稳定性最高。

// 测试文件顶部声明mock,替换为你项目中ApiClient的实际路径
jest.mock('@/utils/ApiClient', () => ({
  ApiClient: {
    getInstance: jest.fn()
  }
}));

// 导入依赖,jest.mock会自动提升到导入语句前执行,无需担心顺序问题
import { ApiClient } from '@/utils/ApiClient';
import { dateFilters } from '@/services/kusto';
import { Workload } from '@/constants/workload';

// 自定义mock返回数据
const mockFetchDateOptionsResponse = {
  data: [/* 你的测试预期返回内容 */]
};

describe("dateFilters()", () => {
  const mockPost = jest.fn();

  beforeEach(() => {
    jest.resetAllMocks();
    // 每次用例执行前重置getInstance返回值,注入mock的post方法
    (ApiClient.getInstance as jest.Mock).mockReturnValue({
      post: mockPost
    });
  });

  it("Mock Fetch API for Date Options Response", async () => {
    // 注入mock响应
    mockPost.mockResolvedValue(mockFetchDateOptionsResponse);

    const response = await dateFilters(Workload.WIN32);

    // 校验调用逻辑
    expect(mockPost).toHaveBeenCalledTimes(1);
    // 可选:校验请求参数是否符合预期
    expect(mockPost).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        db: expect.any(String),
        csl: expect.any(String)
      }),
      expect.objectContaining({
        headers: expect.objectContaining({
          "x-ms-kql-queryName": "win32DateFilters"
        }),
        timeout: expect.any(Number)
      })
    );
    expect(response).toEqual(mockFetchDateOptionsResponse);
  });
});

方案2:Mock底层axios模块(适合需要验证客户端配置的场景)

如果单测需要覆盖ApiClient本身的拦截器、重试配置逻辑,不想直接mock掉整个ApiClient类,可以从底层mock axios的创建逻辑,注意必须手动重置单例的静态缓存。

// 顶部mock axios和axios-retry依赖
jest.mock('axios', () => ({
  __esModule: true,
  default: {
    create: jest.fn()
  }
}));
jest.mock('axios-retry', () => ({
  __esModule: true,
  default: jest.fn()
}));
// 单独mock拦截器依赖的getKustoToken,避免拦截器执行时走真实逻辑
jest.mock('@/utils/auth', () => ({
  getKustoToken: jest.fn().mockResolvedValue('mock-token')
}));

import Axios from 'axios';
import { ApiClient } from '@/utils/ApiClient';
import { dateFilters } from '@/services/kusto';
import { Workload } from '@/constants/workload';

const mockFetchDateOptionsResponse = {
  data: [/* 测试预期返回 */]
};

describe("dateFilters()", () => {
  const mockPost = jest.fn();
  const mockRequestUse = jest.fn();
  const mockResponseUse = jest.fn();

  beforeEach(() => {
    jest.resetAllMocks();
    // 核心:清空单例静态缓存,避免上一个用例的实例污染当前测试
    (ApiClient as any).instance = undefined;
    // 给axios.create注入mock实例
    (Axios.create as jest.Mock).mockReturnValue({
      post: mockPost,
      interceptors: {
        request: { use: mockRequestUse },
        response: { use: mockResponseUse }
      }
    });
    mockPost.mockResolvedValue(mockFetchDateOptionsResponse);
  });

  it("Mock Fetch API for Date Options Response", async () => {
    const response = await dateFilters(Workload.WIN32);
    expect(mockPost).toHaveBeenCalledTimes(1);
    expect(response).toEqual(mockFetchDateOptionsResponse);
  });
});

避坑说明

  • 不要通过增加jest.setTimeout超时时间解决这类报错:超时本质是mock未生效走了真实网络,加超时只会延长失败等待时间,无法解决根本问题。
  • 单例模式的静态属性缓存不会被jest.resetAllMocks自动清空,只要测试涉及直接操作ApiClient类,必须在beforeEach中手动将静态instance属性置空,避免跨用例污染。
  • 如果选择方案2,所有拦截器、插件中引用的外部依赖(比如getKustoToken)都需要单独mock,否则执行到对应逻辑时还是会触发真实调用报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 22:45:47