Windows正常运行的Jest Axios Mock在Mac上失效求助
Jest Axios Mock在Mac环境失效问题求助
我们团队此前仅使用Windows开发环境,近期引入Mac设备后,发现原本在Windows上正常工作的Jest Axios Mock逻辑完全失效——Mock被直接忽略,代码实际发起了对外部vCenter实例的REST请求。Windows环境中调试可看到流程进入mockGet的mockImplementation,但Mac环境中完全不执行Mock逻辑,因实际调用外部服务而失败。
相关Mock代码片段
const mockGet = jest.fn(); const mockPost = jest.fn(); const mockDelete = jest.fn(); // Mock the AxiosWrapper constructor (AxiosWrapper as jest.MockedClass<typeof AxiosWrapper>).mockImplementation(() => ({ get: mockGet, post: mockPost, delete: mockDelete, } as unknown as AxiosWrapper)); ... it('example test', async () => { mockGet.mockImplementation<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => { const baseResponse: AxiosResponse = { data: {}, status: 200, statusText: 'OK', headers: {}, config: { ...config, headers: config?.headers ?? {}, } as InternalAxiosRequestConfig, }; if (url.includes(createFileUrl)) { return Promise.resolve({ ...baseResponse, data: 'fileContent' } as AxiosResponse<T>); } ...
完整相关代码
AxiosWrapper.ts
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios'; import { JsonObject, ensureError } from 'Common-Utils'; import https from 'https'; import { logger } from './common/logger'; import { getEnv } from './index'; class AxiosWrapper { private apiHost: string; private sessionKey: string; private axiosConfig: AxiosRequestConfig; private vcenterUsername: string; private vcenterPassword: string; private httpsAgent: https.Agent; private axiosAuthConfig: object; private vcenterUsernamePasswordBase64: string; constructor(httpsAgent: https.Agent) { this.apiHost = getEnv('API_HOST'); this.vcenterUsername = getEnv('VCENTER_USERNAME'); this.vcenterPassword = getEnv('VCENTER_PASSWORD'); this.httpsAgent = httpsAgent; this.vcenterUsernamePasswordBase64 = Buffer.from(`${this.vcenterUsername}:${this.vcenterPassword}`).toString('base64'); this.sessionKey = ''; this.axiosConfig = { httpsAgent: this.httpsAgent, headers: { 'vmware-api-session-id': this.sessionKey, }, }; this.axiosAuthConfig = { httpsAgent: this.httpsAgent, headers: { Authorization: `Basic ${this.vcenterUsernamePasswordBase64}`, }, }; } async getSessionKey(): Promise<string> { let response; try { response = await axios.post(`https://${this.apiHost}/api/session`, {}, this.axiosAuthConfig); } catch (err) { const error = ensureError(err); logger.error(`Error getting sessionKey: ${error.message}`); throw new Error(`Error getting sessionKey: ${error.message}`, { cause: error }); } return response.data; } async refreshSessionKey() { this.sessionKey = await this.getSessionKey(); this.axiosConfig = { httpsAgent: this.httpsAgent, headers: { 'vmware-api-session-id': this.sessionKey, }, }; } private async request<T>( method: Method, url: string, data?: JsonObject | string, config?: AxiosRequestConfig, ): Promise<AxiosResponse<T>> { const requestConfig: AxiosRequestConfig = { ...this.axiosConfig, ...config, method, url, data, }; try { if (!this.sessionKey) { await this.refreshSessionKey(); if (requestConfig.headers) { requestConfig.headers['vmware-api-session-id'] = this.sessionKey; } } logger.verbose(`Making ${method} request to ${url}`); const response: AxiosResponse<T> = await axios(requestConfig); return response; } catch (error) { logger.error(`Error making ${method} request to ${url}: ${error.message}`); if (method === 'DELETE' && url.includes('session') && error.response?.status === 401) { // this is not a big deal especially if the session key is expired logger.warn(`failed to delete session key: ${this.sessionKey}. ${error.message}`); } else if (axios.isAxiosError(error) && error.response?.status === 401) { logger.warn('Session key expired. Refreshing session key'); await this.refreshSessionKey(); // Retry the request with the new session key requestConfig.headers = { ...requestConfig.headers, 'vmware-api-session-id': this.sessionKey, }; logger.verbose(`Retrying ${method} request to ${url}`); const retryResponse: AxiosResponse<T> = await axios(requestConfig); return retryResponse; } logger.error(`failed making ${method} request to ${url}: ${error.stack}`); throw error; } } async get<T>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> { return this.request<T>('GET', url, undefined, config); } async delete<T>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> { return this.request<T>('DELETE', url, undefined, config); } async post<T>(url: string, data?: JsonObject, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> { return this.request<T>('POST', url, data, config); } } export default AxiosWrapper;
vSphereClient.ts(调用示例)
public async getInternalVMName(name: string): Promise<string> { const response = await this.AxiosWrapper.get<JsonObject>( `https://${this.apiHost}/api/vcenter/vm?names=${name}`, this.axiosConfig, ); if (!response?.data?.[0]?.vm) { throw new Error('Could not get internal VM name'); } logger.verbose(`Internal VM Name: ${response?.data?.[0]?.vm}`); return response.data[0].vm; }
vSphereClient.spec.ts(测试示例)
import path from 'path'; import { VSphereClient } from '../../lib/vSphereClient'; import AxiosWrapper from '../../lib/AxiosWrapper'; const sessionKey = 'session-key'; const apiHost = 'api-host.example.com'; const internalRegisteredVmName = 'vm-145'; jest.mock('../../lib/AxiosWrapper'); jest.mock('Common-Utils', () => ({ sleep: jest.fn(), })); jest.useFakeTimers(); const mockExecuteCommand = jest.spyOn(require('../../lib/common/executeCmd'), 'executeCommand'); mockExecuteCommand.mockImplementation(() => ({ stdout: 'TEST_PASS', stderr: 'TEST_FAILED', error: 'ERROR' })); // Create a mock implementation of AxiosWrapper const mockAxiosWrapper = { get: jest.fn(), post: jest.fn(), delete: jest.fn(), }; // @ts-ignore jest.mocked(AxiosWrapper).mockImplementation(() => mockAxiosWrapper); describe('VSphereClient', () => { afterEach(() => { jest.resetAllMocks(); }); it('getInternalVmName', async () => { mockAxiosWrapper.post.mockImplementation(async (url) => { if (url.includes('/api/session')) { return Promise.resolve(sessionKey); } return Promise.reject(new Error('Unexpected URL')); }); mockAxiosWrapper.get.mockImplementation((url) => { if (url.includes(`vm?names=${internalRegisteredVmName}`)) { return Promise.resolve({}); } return Promise.reject(new Error('Unexpected URL')); }); const vSphereClient = await VSphereClient.Connection.create(); await expect(vSphereClient.getInternalVMName(internalRegisteredVmName)).rejects.toThrowError('Could not get internal VM name'); }); });
错误信息
2025-01-23 09:24:10 undefined error: failed making GET request to https://api-host.example.com/api/vcenter/vm?names=vm-145: Error: getaddrinfo ENOTFOUND api-host.example.com at Function.Object.<anonymous>.AxiosError.from (/Users/jake/Projects/VMware-Utils/node_modules/axios/lib/core/AxiosError.js:89:14) at RedirectableRequest.handleRequestError (/Users/jake/Projects/VMware-Utils/node_modules/axios/lib/adapters/http.js:620:25) at RedirectableRequest.emit (node:events:519:28) at ClientRequest.eventHandlers.<computed> (/Users/jake/Projects/VMware-Utils/node_modules/follow-redirects/index.js:38:24) at ClientRequest.emit (node:events:519:28) at TLSSocket.socketErrorListener (node:_http_client:500:9) at TLSSocket.emit (node:events:519:28) at emitErrorNT (node:internal/streams/destroy:169:8) at emitErrorCloseNT (node:internal/streams/destroy:128:3) at processTicksAndRejections (node:internal/process/task_queues:82:21) at Axios.request (/Users/jake/Projects/VMware-Utils/node_modules/axios/lib/core/Axios.js:45:41) at processTicksAndRejections (node:internal/process/task_queues:95:5) at AxiosWrapper.request (/Users/jake/Projects/VMware-Utils/lib/AxiosWrapper.ts:89:42)
求各位提供解决思路,感谢。
内容的提问来源于stack exchange,提问作者jstokes
相关产品推荐
相关产品推荐

