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

使用axios-mock-adapter模拟Spotify Post API请求失败排查

使用axios-mock-adapter模拟Spotify令牌请求的问题排查与解决

问题场景

我在尝试用axios-mock-adapter模拟Spotify API的令牌获取请求时遇到了两个问题:

  • 初始测试中,mock的POST请求未被触发,tokenResponse始终为undefined,调试显示mock已正确配置、请求参数匹配,但问题依旧。
  • 后续为AuthManager创建了统一的axios实例,确保业务代码和测试代码使用同一实例,但mock仍不生效,反而触发了真实的API请求。

业务代码片段:

export const discovery = {
  authorizationEndpoint: "https://accounts.spotify.com/authorize",
  tokenEndpoint: "https://accounts.spotify.com/api/token",
};

export const getTokenRequest = async (response: AuthSessionResult, request: AuthRequest) => {  
  const accessCode  = response.params.code;
  console.log("Successfully authorised user and obtained access code", accessCode);
  await AsyncStorage.setItem("code", accessCode);

  const codeVerifier = request?.codeVerifier || ""
  console.log("Successfully obtained code verifier from request", codeVerifier);
  await AsyncStorage.setItem("codeVerifier", codeVerifier);

  const data = {
    client_id: clientId,
    grant_type: "authorization_code",
    code: accessCode,
    redirect_uri: redirectUri,
    code_verifier: codeVerifier
  }

  try {
    const tokenResponse = await axios.post(
      discovery.tokenEndpoint,
      qs.stringify(data),
      { 
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        }
      })
    console.log("Successfully obtained token response", tokenResponse);

    storeAccessToken(tokenResponse);
    storeRefreshToken(tokenResponse);
  } catch(error) {
    console.log("Failed to obtain token request", error)
  }
}

测试代码片段:

it("obtains an access code and a refresh token from the authorization code", async () => {
  const mockAxiosAdapter = new MockAdapter(axios);
  const mockTokenResponse = { data: { access_token: "DEF", refresh_token: "ABC" } };
  
  const authResult = { type: "success", params: { code: "123" } }
  const authRequest = { codeVerifier: "XYZ" }

  mockAxiosAdapter.onPost(AuthManager.discovery.tokenEndpoint).reply(200, mockTokenResponse);
  await AuthManager.getTokenRequest(authResult as AuthSessionResult, authRequest as AuthRequest);
  
  expect(AsyncStorage.setItem).toHaveBeenCalledWith("access_token", "DEF");
  expect(AsyncStorage.setItem).toHaveBeenCalledWith("refresh_token", "ABC");
})

问题根源分析

  1. axios实例不一致:业务代码若使用全局axios,测试却mock自定义实例,或反之,都会导致mock不生效;统一实例后仍触发真实请求,可能是实例引用错误或mock配置时机不对。
  2. 请求匹配严格性:axios-mock-adapter对请求的URL、头信息、请求体格式是严格匹配的,任何细节不匹配都会导致mock不触发。

具体修复方案

1. 强制业务与测试使用同一axios实例

在AuthManager中创建并导出专属axios实例,业务代码和测试代码都使用这个实例:

// AuthManager.js
import axios from 'axios';

// 创建统一实例并导出
export const axiosInstance = axios.create();

export const discovery = {
  authorizationEndpoint: "https://accounts.spotify.com/authorize",
  tokenEndpoint: "https://accounts.spotify.com/api/token",
};

// 业务代码中使用该实例发送请求
export const getTokenRequest = async (response: AuthSessionResult, request: AuthRequest) => {
  // ... 其他代码 ...
  const tokenResponse = await axiosInstance.post(
    discovery.tokenEndpoint,
    qs.stringify(data),
    { 
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      }
    }
  )
  // ... 其他代码 ...
}

测试代码中mock这个导出的实例:

// 测试文件
import { axiosInstance, AuthManager } from './AuthManager';
import MockAdapter from 'axios-mock-adapter';

it("obtains an access code and a refresh token from the authorization code", async () => {
  const mockAxiosAdapter = new MockAdapter(axiosInstance);
  // 注意:reply的响应数据不需要包裹在data字段中,axios会自动处理
  const mockTokenData = { access_token: "DEF", refresh_token: "ABC" };
  
  const authResult = { type: "success", params: { code: "123" } }
  const authRequest = { codeVerifier: "XYZ" }

  mockAxiosAdapter.onPost(AuthManager.discovery.tokenEndpoint).reply(200, mockTokenData);
  
  await AuthManager.getTokenRequest(authResult as AuthSessionResult, authRequest as AuthRequest);
  
  expect(AsyncStorage.setItem).toHaveBeenCalledWith("access_token", "DEF");
  expect(AsyncStorage.setItem).toHaveBeenCalledWith("refresh_token", "ABC");
})

2. 宽松匹配请求(可选)

如果请求体或URL存在动态内容,可以用正则表达式匹配URL,或忽略请求体校验:

// 用正则匹配URL后缀
mockAxiosAdapter.onPost(/\/api\/token$/).reply(200, mockTokenData);

// 或仅匹配请求方法和URL,忽略其他细节
mockAxiosAdapter.onPost(AuthManager.discovery.tokenEndpoint).reply(200, mockTokenData);

3. 验证mock触发状态

在测试中添加断言,确认请求是否命中mock:

await AuthManager.getTokenRequest(authResult as AuthSessionResult, authRequest as AuthRequest);
// 确认POST请求被触发了一次
expect(mockAxiosAdapter.history.post.length).toBe(1);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 14:22:44