资讯动态

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

发布时间:2026/9/7 3:24:49 来源:尧图企业网站定制
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 请求的代码推荐做法是直接模拟mockaxios 模块让测试完全不触碰真实网络。这样做的好处是测试执行快、结果确定不受网络抖动和外部服务状态影响你可以精确控制每个方法返回什么数据、状态、异常从而逐一覆盖业务代码的分支与 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.get、axios.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 response和this.status response.status也就是说上面的例子中mockError.status就是404业务里error.status 404的分支可以被精确命中每个AxiosError都带有isAxiosError true标记配合 lib/helpers/isAxiosError.js 可以在 catch 中区分 axios 错误与其他异常常用错误码在源码中定义为静态属性如ERR_BAD_REQUEST、ECONNABORTED、ETIMEDOUT、ERR_NETWORK、ERR_CANCELED、ERR_BAD_RESPONSE等同样位于 lib/core/AxiosError.js 的类定义之后。构造模拟错误时优先从这套常量里选 code可以让测试数据与真实行为保持同一套词汇表若模拟的是网络层失败没有response可以省略后三个参数只传message与code例如ETIMEDOUT此时mockError.response为undefined正好用于覆盖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判断函数、null、false。请求拦截器在派发前就已把 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.jsaxios-mock-adapter绑定在该新实例上后reply的回调参数就是流经请求拦截器之后的最终 config把它捕获下来即可断言拦截器的副作用如上面的Authorization头。axios v1 的config.headers是AxiosHeaders对象示例中用config.headers.set(...)写入正对应 lib/core/AxiosHeaders.js 提供的 API。实践建议官方文档给出的三条经验规则始终在模块级别做模拟或直接用MockAdapter——避免在共享实例上逐个 mock 方法因为状态可能在测试之间泄漏优先使用mockResolvedValueOnce/mockRejectedValueOnce而非mockResolvedValue这样每个用例只消费一次打桩测试之间互不干扰测试重试逻辑时用MockAdapter这样被测的拦截器在每一次重试尝试中都会真实执行而不是被模块级 mock 短路掉。延伸axios 仓库自身的测试组织如果想参考一个成熟 HTTP 客户端如何组织测试本仓库的 tests/ 目录值得一看说明见 tests/README.mdtests/unit/聚焦单元行为网络相关测试通过 tests/setup/server.js 启动本地测试服务器startHTTPServer/stopHTTPServer并用try/finally保证清理tests/browser/浏览器运行时行为采用文件内MockXMLHttpRequest风格的手写 mock在beforeEach替换全局 XHR、afterEach恢复可参考 tests/browser/adapter.browser.test.jstests/smoke/esm与tests/smoke/cjsESM/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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价