news 2026/9/7 3:24:49

Axios 测试实战指南:模块级 Mock、AxiosError 构造与 axios-mock-adapter 集成测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Axios 测试实战指南:模块级 Mock、AxiosError 构造与 axios-mock-adapter 集成测试

Axios 测试实战指南:模块级 Mock、AxiosError 构造与 axios-mock-adapter 集成测试

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

本文以 Axios 官方测试指南 为核心,系统讲解如何测试依赖 axios 发起 HTTP 请求的业务代码:先用 Vitest/Jest 对 axios 模块本身做 Mock,再演示如何手动构造AxiosError覆盖错误分支,以及如何使用axios-mock-adapter在适配层做更贴近集成场景的模拟。读完本文,你能够编写不依赖真实网络、可重复运行且覆盖成功/失败/超时等路径的 axios 请求测试。

为什么推荐 Mock axios 本身

测试涉及 HTTP 请求的代码,推荐做法是直接模拟(mock)axios 模块,让测试完全不触碰真实网络。这样做的好处是:

  • 测试执行快、结果确定,不受网络抖动和外部服务状态影响;
  • 你可以精确控制每个方法返回什么(数据、状态、异常),从而逐一覆盖业务代码的分支;
  • 与 axios 自身的测试栈一致——该仓库主测试框架即 Vitest(见 package.json 中"test": "npm run test:vitest",对应脚本vitest run),业务侧用同一套工具链迁移成本最低。

使用 Vitest 或 Jest 进行模块级模拟

Vitest 与 Jest 都支持模块级模拟(vi.mock/jest.mock)。你可以把整个 axios 模块替换为 mock,并控制每个方法的返回值。

被测业务代码示例:

// user-service.js import axios from "axios"; export async function getUser(id) { const { data } = await axios.get(`/api/users/${id}`); return data; }

对应的测试文件:

// user-service.test.js import { describe, it, expect, vi } from "vitest"; import axios from "axios"; import { getUser } from "./user-service"; vi.mock("axios"); describe("getUser", () => { it("returns user data on success", async () => { const mockUser = { id: 1, name: "Jay" }; // 让 axios.get 以我们的假响应 resolve axios.get.mockResolvedValueOnce({ data: mockUser }); const result = await getUser(1); expect(result).toEqual(mockUser); expect(axios.get).toHaveBeenCalledWith("/api/users/1"); }); it("throws when the request fails", async () => { axios.get.mockRejectedValueOnce(new Error("Network error")); await expect(getUser(1)).rejects.toThrow("Network error"); }); });

几个要点:

  • vi.mock("axios")会自动把axios.getaxios.post等导出方法替换为 mock 函数,因此可以直接调用axios.get.mockResolvedValueOnce(...)等断言/打桩 API;
  • 第一个用例验证了「返回值」与「调用参数」两个可观测行为:result应等于data,且axios.get收到了拼接好的 URL/api/users/1
  • 第二个用例通过mockRejectedValueOnce模拟请求失败,验证业务函数会把拒绝正确传播出去。

手动构造 AxiosError 覆盖错误处理分支

如果你的业务代码会检查error.response(比如根据error.response.status做 404 降级、根据error.code判断超时),可以直接new一个AxiosError实例,而不必依赖 mock 库:

import axios, { AxiosError } from "axios"; import { vi } from "vitest"; const mockError = new AxiosError( "Not Found", "ERR_BAD_REQUEST", {}, // config {}, // request { // response status: 404, statusText: "Not Found", data: { message: "User not found" }, headers: {}, config: {}, } ); axios.get.mockRejectedValueOnce(mockError);

结合源码可以确认这种手写错误对象与真实运行时抛出的错误在结构上是一致的:

  • 构造函数签名为constructor(message, code, config, request, response),见 lib/core/AxiosError.js。传入response后,实例会自动获得this.response = responsethis.status = response.status,也就是说上面的例子中mockError.status就是404,业务里error.status === 404的分支可以被精确命中;
  • 每个AxiosError都带有isAxiosError = true标记,配合 lib/helpers/isAxiosError.js 可以在 catch 中区分 axios 错误与其他异常;
  • 常用错误码在源码中定义为静态属性(如ERR_BAD_REQUESTECONNABORTEDETIMEDOUTERR_NETWORKERR_CANCELEDERR_BAD_RESPONSE等),同样位于 lib/core/AxiosError.js 的类定义之后。构造模拟错误时优先从这套常量里选 code,可以让测试数据与真实行为保持同一套词汇表;
  • 若模拟的是网络层失败(没有response),可以省略后三个参数,只传messagecode(例如ETIMEDOUT),此时mockError.responseundefined,正好用于覆盖if (error.response)的 falsy 分支。

使用 axios-mock-adapter 在适配层模拟

axios-mock-adapter 是一个第三方库,它会往你的 axios 实例上安装一个自定义适配器,在适配层拦截请求。关键区别在于:请求仍然走完整的 axios 管线——你的拦截器依然会执行——因此它更适合集成测试场景。

安装:

npm install --save-dev axios-mock-adapter

基本用法:

import axios from "axios"; import MockAdapter from "axios-mock-adapter"; const mock = new MockAdapter(axios); // 模拟一个 GET 请求 mock.onGet("/api/users/1").reply(200, { id: 1, name: "Jay" }); // 模拟一个 POST 请求 mock.onPost("/api/users").reply(201, { id: 2, name: "New User" }); // 模拟网络错误 mock.onGet("/api/failing").networkError(); // 模拟请求超时 mock.onGet("/api/slow").timeout();

在测试之间重置模拟,避免用例互相污染:

afterEach(() => { mock.reset(); // 清除所有已注册的处理函数 });

为什么"拦截器仍然执行"?从源码结构看:lib/core/dispatchRequest.js 中通过adapters.getAdapter(config.adapter || defaults.adapter, config)解析适配器,而 lib/adapters/adapters.js 的getAdapter同时接受适配器名称和自定义函数isResolvedHandle判断函数、nullfalse)。请求拦截器在派发前就已把 config 加工完毕(请求拦截 →dispatchRequest→ 适配器 → 响应拦截),axios-mock-adapter只是替换了管线末端这个可插拔的适配器函数,前后两段的拦截器逻辑完全不受影响。仓库自带的 tests/unit/adapters/adapters.test.js 也验证了「按函数句柄加载自定义适配器」这一能力。

隔离测试拦截器

要单独测试某个拦截器(例如统一注入鉴权头),应在测试里创建新的 axios 实例,而不是复用全局共享实例:

import axios from "axios"; import MockAdapter from "axios-mock-adapter"; describe("auth interceptor", () => { it("attaches a Bearer token to every request", async () => { const instance = axios.create(); const mock = new MockAdapter(instance); // 添加你的拦截器 instance.interceptors.request.use((config) => { config.headers.set("Authorization", "Bearer test-token"); return config; }); // 通过检查 mock 收到的内容来捕获请求配置 let capturedConfig; mock.onGet("/api/data").reply((config) => { capturedConfig = config; return [200, {}]; }); await instance.get("/api/data"); expect(capturedConfig.headers["Authorization"]).toBe("Bearer test-token"); }); });

两个实现层面的依据:

  • 每个 axios 实例在构造时都会创建自己独立的interceptors.request/interceptors.response两个InterceptorManager,见 lib/core/Axios.js 的构造函数。因此axios.create()出来的实例不会继承默认实例上别人注册的拦截器,天然隔离;InterceptorManager通过use/eject/clear管理拦截器栈,见 lib/core/InterceptorManager.js;
  • axios-mock-adapter绑定在该新实例上后,reply的回调参数就是流经请求拦截器之后的最终 config,把它捕获下来即可断言拦截器的副作用(如上面的Authorization头)。axios v1 的config.headersAxiosHeaders对象,示例中用config.headers.set(...)写入,正对应 lib/core/AxiosHeaders.js 提供的 API。

实践建议

官方文档给出的三条经验规则:

  • 始终在模块级别做模拟(或直接用MockAdapter——避免在共享实例上逐个 mock 方法,因为状态可能在测试之间泄漏;
  • 优先使用mockResolvedValueOnce/mockRejectedValueOnce而非mockResolvedValue,这样每个用例只消费一次打桩,测试之间互不干扰;
  • 测试重试逻辑时用MockAdapter,这样被测的拦截器在每一次重试尝试中都会真实执行,而不是被模块级 mock 短路掉。

延伸:axios 仓库自身的测试组织

如果想参考一个成熟 HTTP 客户端如何组织测试,本仓库的 tests/ 目录值得一看(说明见 tests/README.md):

  • tests/unit/:聚焦单元行为,网络相关测试通过 tests/setup/server.js 启动本地测试服务器(startHTTPServer/stopHTTPServer),并用try/finally保证清理;
  • tests/browser/:浏览器运行时行为,采用文件内MockXMLHttpRequest风格的手写 mock,在beforeEach替换全局 XHR、afterEach恢复(可参考 tests/browser/adapter.browser.test.js);
  • tests/smoke/esmtests/smoke/cjs:ESM/CJS 两套打包格式的对齐冒烟测试,验证导入与关键请求流程;
  • 文件命名约定:单元测试*.test.js、浏览器测试*.browser.test.js、冒烟测试*.smoke.test.js/*.smoke.test.cjs

其中「浏览器测试用手写 XHR mock 替换全局对象并在清理钩子中恢复」的思路,与本文推荐的"在共享实例上不留 mock 残留"的原则完全一致,可以直接借鉴到自己的测试基建中。

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 3:24:47

YOLOv8 Linux训练完整指南:从环境搭建到参数调优

简介:面向Linux服务器上YOLOv8自定义数据集训练的项目代码包,适合深度学习和计算机视觉开发者使用。资源涵盖从环境检测、数据集转换到模型训练与验证的完整流程,尤其解决了XML标注转YOLO txt格式、yaml配置编写以及单卡/多卡启动等常见痛点&…

作者头像 李华
网站建设 2026/9/7 3:22:21

IC烧录全解析:从原理到量产,芯片落地的隐形门槛

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 3:21:47

合伙人散伙复盘:一半因为钱,一半因为验证码逼出来的内耗

合伙人散伙复盘:一半因为钱,一半因为验证码逼出来的内耗 一次散伙饭上的真话: 「散伙的直接原因是分钱分歧,但根子早烂了——三个人做店群,验证码没人愿意管又不能没人管。排班排到吵架,谁盯得多谁觉得自己…

作者头像 李华
网站建设 2026/9/7 3:21:28

大规模搜索服务中的GPU嵌入推理与批处理优化实践

做搜索服务的人,这两年大概率逃不开一个话题:怎么在检索链路里塞入向量召回、怎么把用户查询和候选文档做embedding、怎么在延迟预算内把模型推理跑完。Perplexity这类AI驱动的大规模搜索结果服务,本质上就是把传统倒排索引的绿色通道变成了一…

作者头像 李华
网站建设 2026/9/7 3:20:19

YOLO实时物体检测实战:齿条、螺栓、螺母与裂纹识别

简介:面向目标检测初学者与工业视觉开发者,这份YOLO实时物体检测资源包聚焦齿条、螺栓、螺母及裂缝检测等典型质检场景,涵盖网络原理、C语言工程实现与配套标签数据。资源共2000个文件,以1903个txt标签/配置数据和49个h头文件、46…

作者头像 李华
网站建设 2026/9/7 3:15:06

C盘清理利器LightC:开源工具一键分析大文件与社交软件缓存

C盘又红了,这是Windows用户最常见的“血压拉满”场景之一。LightC 就是我从GitHub上翻到的一款专门解决这个问题的开源工具,它的定位很简单:一键扫描C盘垃圾文件、分析大文件、单独清理微信和QQ缓存、给系统瘦身。如果你电脑C盘常年告急&…

作者头像 李华