MSW已拦截请求但仍触发Axios网络错误问题求助
问题:MSW拦截Axios请求后仍出现网络错误?
我现在卡壳了——查了好几篇Axios+MSW的社区帖子,但还是没找到问题。我想用MSW拦截Axios发起的网络请求,注入测试响应,但测试一直失败,报网络错误,而且确认MSW已经拦截到请求了。
测试文件(执行失败,调试中)
/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { jobsDashboardResponse } from "../../../tests/mocks/msw/handlers" import { server as mswServer } from "../../../tests/mocks/msw/server" import { bacalhauAPI } from "../bacalhau" // Enable request interception. beforeAll(() => mswServer.listen()) // Reset handlers so that each test could alter them // without affecting other, unrelated tests. afterEach(() => mswServer.resetHandlers()) // Don't forget to clean up afterwards. afterAll(() => mswServer.close()) describe("Basic fetch of mocked API", () => { it("should GET /orchestrator/jobs", async () => { const jobsResponse = { a: 2 } mswServer.use(jobsDashboardResponse) mswServer.listHandlers() const jobs = await bacalhauAPI.listJobs() expect(jobs).toEqual(jobsResponse) }) })
BacalhauAPI代码
import axios, { AxiosInstance } from "axios" import { JobListRequest, } from "../helpers/jobInterfaces" class BacalhauAPI { apiClient: AxiosInstance constructor(baseURL: string) { this.apiClient = axios.create({ baseURL, headers: { "Content-Type": "application/json", }, }) } async listJobs(labels?: string[], nextToken?: string): Promise<JobsResponse> { try { const params: JobListRequest = { order_by: "created_at", reverse: true, limit: 10, labels: labels ? `env in (${labels.join(",")})` : undefined, next_token: nextToken, } const response = await this.apiClient.get("/orchestrator/jobs", { params, }) return response.data as JobsResponse } catch (error) { console.error("An error occurred while listing jobs:", error) throw error } } } function getAPIConfig( property: "host" | "port" | "base", defaultValue: string ): string { const declared = document .querySelector(`link[rel=api-${property}]`) ?.getAttribute("href") const useDefault = declared === undefined || declared?.match(/\{{2}/) || declared === "" || declared === null return useDefault ? defaultValue : declared || "" } const host = getAPIConfig("host", document.location.hostname) const port = getAPIConfig("port", "1234") const base = getAPIConfig("base", "api/v1") export const bacalhauAPI = new BacalhauAPI( `${document.location.protocol}//${host}:${port}/${base}` )
MSW handlers代码
import { http, HttpResponse, RequestHandler, RequestHandlerOptions } from "msw"; import { TestData } from "../../basic/msw.tests"; export const jobsDashboardResponse = http.get('http://localhost:1234/api/v1/orchestrator/jobs', ({ cookies }) => { // Placeholders for messing around with cookies const { v } = cookies return HttpResponse.json(v === 'a' ? { foo: 'a' } : { bar: 'b' }) }) export const handlers: RequestHandler<any, any, any, RequestHandlerOptions>[] = [jobsDashboardResponse]
MSW server代码
import { setupServer } from "msw/node" import { handlers } from "./handlers" export const server = setupServer(...handlers)
错误信息
console.error An error occurred while listing jobs: AxiosError { message: 'Network Error', name: 'AxiosError', code: 'ERR_NETWORK', config: { transitional: { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }, adapter: [ 'xhr', 'http' ], transformRequest: [ [Function: transformRequest] ], transformResponse: [ [Function: transformResponse] ], timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, env: { FormData: [Function], Blob: [class Blob] }, validateStatus: [Function: validateStatus], headers: Object [AxiosHeaders] { Accept: 'application/json, text/plain, */*', 'Content-Type': null }, baseURL: 'http://localhost:1234/api/v1', params: { order_by: 'created_at', reverse: true, limit: 10, labels: undefined, next_token: undefined }, method: 'get', url: '/orchestrator/jobs', data: undefined }, request: XMLHttpRequest { open: [Function: open], setRequestHeader: [Function: setRequestHeader], send: [Function: send], abort: [Function: abort], getResponseHeader: [Function: getResponseHeader], getAllResponseHeaders: [Function: getAllResponseHeaders], overrideMimeType: [Function: overrideMimeType], onreadystatechange: [Getter/Setter], readyState: 4, timeout: [Getter/Setter], withCredentials: [Getter/Setter], upload: [Getter], responseURL: [Getter], status: [Getter], statusText: [Getter], responseType: [Getter/Setter], response: [Getter], responseText: [Getter], responseXML: [Getter], UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4 } }
问题核心
确认MSW已拦截到请求,但仍抛出ERR_NETWORK错误,请问问题出在哪?
可能的原因及修复方案
1. URL匹配不完整(查询参数导致)
MSW默认严格匹配完整URL,包括查询参数。你的Axios请求会带上order_by、reverse等参数,但MSW handler监听的是不带参数的URL,导致匹配失败,请求落到真实网络触发错误。
修复:
使用通配符忽略查询参数,或改用相对路径匹配:
// 方案1:用通配符匹配带参数的URL export const jobsDashboardResponse = http.get('http://localhost:1234/api/v1/orchestrator/jobs*', ({ cookies }) => { return HttpResponse.json(cookies.v === 'a' ? { foo: 'a' } : { bar: 'b' }) }) // 方案2:使用相对路径,依赖baseURL自动拼接 export const jobsDashboardResponse = http.get('/api/v1/orchestrator/jobs', ({ cookies }) => { return HttpResponse.json(cookies.v === 'a' ? { foo: 'a' } : { bar: 'b' }) })
2. 测试环境的协议错误
在Node.js测试环境(如Jest)中,document.location.protocol可能为file:而非http:,导致Axios的baseURL变成无效的file://localhost:1234/api/v1,触发网络错误。
修复:
在测试中覆盖baseURL,或修改API初始化逻辑适配非浏览器环境:
// 测试文件中添加:在启动MSW后覆盖baseURL beforeAll(() => { mswServer.listen() bacalhauAPI.apiClient.defaults.baseURL = 'http://localhost:1234/api/v1' }) // 或修改BacalhauAPI的协议逻辑 const protocol = typeof window === 'undefined' ? 'http:' : document.location.protocol export const bacalhauAPI = new BacalhauAPI( `${protocol}//${host}:${port}/${base}` )
3. Axios适配器冲突
MSW在Node.js环境依赖拦截http模块,但Axios默认同时启用xhr和http适配器,xhr适配器无法被MSW正确拦截,导致请求走真实网络。
修复:
强制Axios在测试环境使用http适配器:
// 测试文件顶部添加 import axios from 'axios' import httpAdapter from 'axios/lib/adapters/http' axios.defaults.adapter = httpAdapter
内容的提问来源于stack exchange,提问作者aronchick
相关产品推荐
相关产品推荐

