Axios拦截器TypeScript单元测试问题求助(v1.6.3)
解决Axios v1.6.3+ TypeScript环境下拦截器单元测试的正确方案
问题根源
Axios从v1.x版本开始,将interceptors.request.handlers标记为内部私有属性,TypeScript类型定义中不再对外暴露该字段,这就是你遇到类型错误的原因。早期案例能使用该方法,要么基于旧版Axios,要么是非TypeScript环境下忽略类型检查的写法,并非当前版本的最佳实践。
正确测试思路
我们不需要依赖Axios内部的handlers属性,而是通过抽离拦截器逻辑为独立函数,直接测试函数的业务逻辑;若需验证拦截器注册行为,可通过Sinon监视Axios的use方法实现。完全符合单元测试的隔离性要求,且不涉及集成测试。
步骤1:重构拦截器代码(抽离独立函数)
先把拦截器的处理逻辑从Axios实例中抽出来,方便单独测试:
// axios-interceptors.ts import { InternalAxiosRequestConfig } from 'axios'; // 拦截器成功回调:处理token添加逻辑 export const requestSuccessHandler = (config: InternalAxiosRequestConfig) => { const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }; // 拦截器错误回调:处理请求错误 export const requestErrorHandler = (error: unknown) => { return Promise.reject(error); }; // 实际业务中注册拦截器的函数 import axios from 'axios'; export const setupRequestInterceptor = () => { axios.interceptors.request.use(requestSuccessHandler, requestErrorHandler); };
步骤2:编写单元测试(覆盖所有场景)
使用Sinon模拟依赖(如localStorage),直接测试抽离的函数,同时验证拦截器的注册行为:
// axios-interceptors.test.ts import { expect } from 'chai'; import sinon from 'sinon'; import axios from 'axios'; import { requestSuccessHandler, requestErrorHandler, setupRequestInterceptor } from './axios-interceptors'; import { InternalAxiosRequestConfig } from 'axios'; describe('Axios Request Interceptors', () => { let localStorageStub: sinon.SinonStub; beforeEach(() => { // 全局模拟localStorage.getItem方法 localStorageStub = sinon.stub(window.localStorage, 'getItem'); }); afterEach(() => { // 恢复原方法,避免污染其他测试 localStorageStub.restore(); }); // 场景1:存在token时,正确添加Authorization头 it('adds Authorization header when token exists', () => { const mockToken = 'test-jwt-token'; localStorageStub.withArgs('authToken').returns(mockToken); const mockConfig: InternalAxiosRequestConfig = { headers: {}, method: 'get', url: '/api/test' }; const result = requestSuccessHandler(mockConfig); expect(result.headers.Authorization).to.equal(`Bearer ${mockToken}`); }); // 场景2:无token时,不添加Authorization头 it('does not add Authorization header when token is missing', () => { localStorageStub.withArgs('authToken').returns(null); const mockConfig: InternalAxiosRequestConfig = { headers: {}, method: 'get', url: '/api/test' }; const result = requestSuccessHandler(mockConfig); expect(result.headers.Authorization).to.be.undefined; }); // 场景3:错误回调正确传递错误 it('rejects with original error in error handler', async () => { const mockError = new Error('Request timeout'); await expect(requestErrorHandler(mockError)).to.be.rejectedWith(mockError); }); // 验证拦截器是否被正确注册到Axios实例 it('registers interceptors to Axios instance', () => { const useSpy = sinon.spy(axios.interceptors.request, 'use'); setupRequestInterceptor(); expect(useSpy.calledWith(requestSuccessHandler, requestErrorHandler)).to.be.true; useSpy.restore(); }); });
方案优势
- 类型安全:完全符合TypeScript类型规范,不会出现属性不存在的错误
- 隔离性强:只测试拦截器的业务逻辑,不依赖Axios内部实现,避免版本更新带来的兼容问题
- 覆盖全面:轻松覆盖有token、无token、错误处理三个核心场景
- 可维护性高:拦截器逻辑与Axios注册逻辑分离,后续修改逻辑无需调整测试代码
内容的提问来源于stack exchange,提问作者Sinan Bayar
相关产品推荐
相关产品推荐

