/** * API客户端测试 * 测试API客户端的核心功能,包括请求、响应处理、拦截器管理等 */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createMockAxiosResponse, createMockAxiosError } from '@/test/mocks/http-client'; import { createMockApiRequestConfig } from '@/test/mocks/data-factory'; import axios from 'axios'; // 使用vi.hoisted提升createMockAxios,避免初始化顺序问题 const { createMockAxios } = vi.hoisted(() => { let interceptorIdCounter = 0; const mockAxiosInstance = { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), patch: vi.fn(), request: vi.fn(), interceptors: { request: { use: vi.fn(() => ++interceptorIdCounter), eject: vi.fn() }, response: { use: vi.fn(() => ++interceptorIdCounter), eject: vi.fn() } }, defaults: { headers: { common: {}, get: {}, post: {}, put: {}, delete: {} } } }; return { createMockAxios: () => mockAxiosInstance }; }); // 模拟axios模块 vi.mock('axios', () => ({ default: { create: vi.fn(() => createMockAxios()) }, AxiosError: class extends Error { code?: string; config?: any; request?: any; response?: any; isAxiosError?: boolean; constructor(message: string, code?: string, config?: any, request?: any, response?: any) { super(message); this.name = 'AxiosError'; this.code = code; this.config = config; this.request = request; this.response = response; this.isAxiosError = true; } } })); // 在模拟设置后导入createApiClient import { createApiClient } from '@/api/client'; // 获取模拟的axios类型 type MockedAxios = ReturnType & { get: ReturnType; post: ReturnType; put: ReturnType; delete: ReturnType; patch: ReturnType; request: ReturnType; interceptors: { request: { use: ReturnType; eject: ReturnType; }; response: { use: ReturnType; eject: ReturnType; }; }; defaults: { timeout?: number; baseURL?: string; }; }; describe('API客户端', () => { let apiClient: ReturnType; let mockAxios: MockedAxios; beforeEach(() => { // 重置所有模拟 vi.clearAllMocks(); // 创建新的API客户端实例 apiClient = createApiClient(); // 获取模拟的axios实例 mockAxios = vi.mocked(axios).create() as MockedAxios; }); describe('基础请求方法', () => { it('应该能够发送GET请求', async () => { const mockData = { id: 1, name: 'Test' }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.get('/test'); expect(mockAxios.request).toHaveBeenCalledWith({ method: 'GET', url: '/test' }); expect(result).toEqual(mockData); }); it('应该能够发送POST请求', async () => { const mockData = { id: 1, name: 'Test' }; const requestData = { name: 'New Item' }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.post('/test', requestData); expect(mockAxios.request).toHaveBeenCalledWith({ method: 'POST', url: '/test', data: requestData }); expect(result).toEqual(mockData); }); it('应该能够发送PUT请求', async () => { const mockData = { id: 1, name: 'Updated Test' }; const requestData = { name: 'Updated Item' }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.put('/test/1', requestData); expect(mockAxios.request).toHaveBeenCalledWith({ method: 'PUT', url: '/test/1', data: requestData }); expect(result).toEqual(mockData); }); it('应该能够发送DELETE请求', async () => { const mockData = { success: true }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.delete('/test/1'); expect(mockAxios.request).toHaveBeenCalledWith({ method: 'DELETE', url: '/test/1' }); expect(result).toEqual(mockData); }); it('应该能够发送PATCH请求', async () => { const mockData = { id: 1, name: 'Patched Test' }; const requestData = { name: 'Patched Item' }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.patch('/test/1', requestData); expect(mockAxios.request).toHaveBeenCalledWith({ method: 'PATCH', url: '/test/1', data: requestData }); expect(result).toEqual(mockData); }); it('应该能够发送通用请求', async () => { const mockData = { id: 1, name: 'Test' }; const config = createMockApiRequestConfig({ url: '/test', method: 'GET' }); mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.request(config); expect(mockAxios.request).toHaveBeenCalledWith(config); expect(result).toEqual(mockData); }); }); describe('返回完整响应的请求方法', () => { it('应该能够返回GET请求的完整响应', async () => { const mockData = { id: 1, name: 'Test' }; const mockResponse = createMockAxiosResponse(mockData); mockAxios.request.mockResolvedValue(mockResponse); const result = await apiClient.request({ url: '/test', method: 'GET' }); expect(mockAxios.request).toHaveBeenCalled(); expect(result).toEqual(mockData); }); it('应该能够返回POST请求的完整响应', async () => { const mockData = { id: 1, name: 'Test' }; const requestData = { name: 'New Item' }; const mockResponse = createMockAxiosResponse(mockData); mockAxios.request.mockResolvedValue(mockResponse); const result = await apiClient.request({ url: '/test', method: 'POST', data: requestData }); expect(mockAxios.request).toHaveBeenCalled(); expect(result).toEqual(mockData); }); }); describe('拦截器管理', () => { it('应该能够添加请求拦截器', () => { const onRequest = (config: any) => config; const onRequestError = (error: any) => error; const interceptorId = apiClient.addRequestInterceptor(onRequest, onRequestError); expect(mockAxios.interceptors.request.use).toHaveBeenCalled(); expect(typeof interceptorId).toBe('number'); }); it('应该能够添加响应拦截器', () => { const onResponse = (response: any) => response; const onResponseError = (error: any) => error; const interceptorId = apiClient.addResponseInterceptor(onResponse, onResponseError); expect(mockAxios.interceptors.response.use).toHaveBeenCalled(); expect(typeof interceptorId).toBe('number'); }); it('应该能够移除请求拦截器', () => { const onRequest = (config: any) => config; const interceptorId = apiClient.addRequestInterceptor(onRequest); apiClient.removeRequestInterceptor(interceptorId); expect(mockAxios.interceptors.request.eject).toHaveBeenCalledWith(interceptorId); }); it('应该能够处理移除不存在的请求拦截器', () => { const nonExistentId = 999; // 不应该抛出错误 expect(() => { apiClient.removeRequestInterceptor(nonExistentId); }).not.toThrow(); // 不应该调用eject方法,因为拦截器不存在 expect(mockAxios.interceptors.request.eject).not.toHaveBeenCalledWith(nonExistentId); }); it('应该能够移除响应拦截器', () => { const onResponse = (response: any) => response; const interceptorId = apiClient.addResponseInterceptor(onResponse); apiClient.removeResponseInterceptor(interceptorId); expect(mockAxios.interceptors.response.eject).toHaveBeenCalledWith(interceptorId); }); it('应该能够处理移除不存在的响应拦截器', () => { const nonExistentId = 999; // 不应该抛出错误 expect(() => { apiClient.removeResponseInterceptor(nonExistentId); }).not.toThrow(); // 不应该调用eject方法,因为拦截器不存在 expect(mockAxios.interceptors.response.eject).not.toHaveBeenCalledWith(nonExistentId); }); it('应该能够添加和移除多个请求拦截器', () => { const onRequest1 = (config: any) => config; const onRequest2 = (config: any) => config; const onRequest3 = (config: any) => config; // 添加三个拦截器 const id1 = apiClient.addRequestInterceptor(onRequest1); const id2 = apiClient.addRequestInterceptor(onRequest2); const id3 = apiClient.addRequestInterceptor(onRequest3); // 移除中间的拦截器 apiClient.removeRequestInterceptor(id2); // 验证eject被调用 expect(mockAxios.interceptors.request.eject).toHaveBeenCalledWith(id2); // 移除剩余的拦截器 apiClient.removeRequestInterceptor(id1); apiClient.removeRequestInterceptor(id3); // 验证所有eject都被调用 expect(mockAxios.interceptors.request.eject).toHaveBeenCalledWith(id1); expect(mockAxios.interceptors.request.eject).toHaveBeenCalledWith(id3); }); it('应该能够添加和移除多个响应拦截器', () => { const onResponse1 = (response: any) => response; const onResponse2 = (response: any) => response; const onResponse3 = (response: any) => response; // 添加三个拦截器 const id1 = apiClient.addResponseInterceptor(onResponse1); const id2 = apiClient.addResponseInterceptor(onResponse2); const id3 = apiClient.addResponseInterceptor(onResponse3); // 移除第一个和第三个拦截器 apiClient.removeResponseInterceptor(id1); apiClient.removeResponseInterceptor(id3); // 验证eject被调用 expect(mockAxios.interceptors.response.eject).toHaveBeenCalledWith(id1); expect(mockAxios.interceptors.response.eject).toHaveBeenCalledWith(id3); // 移除剩余的拦截器 apiClient.removeResponseInterceptor(id2); // 验证所有eject都被调用 expect(mockAxios.interceptors.response.eject).toHaveBeenCalledWith(id2); }); }); describe('配置管理', () => { it('应该能够设置默认配置', () => { const config = { timeout: 5000 }; apiClient.setDefaults(config); expect(mockAxios.defaults.timeout).toBe(5000); }); it('应该能够设置基础URL', () => { const baseURL = 'https://api.example.com'; apiClient.setBaseURL(baseURL); expect(mockAxios.defaults.baseURL).toBe(baseURL); }); }); describe('实例创建', () => { it('应该能够创建新实例', () => { const newConfig = { timeout: 8000 }; const newInstance = apiClient.createInstance(newConfig); expect(newInstance).toBeDefined(); expect(typeof newInstance.get).toBe('function'); expect(typeof newInstance.post).toBe('function'); }); it('应该能够复制拦截器到新实例', () => { // 添加请求拦截器 const onRequest = (config: any) => ({ ...config, intercepted: true }); const onRequestError = (error: any) => error; const requestInterceptorId = apiClient.addRequestInterceptor(onRequest, onRequestError); // 添加响应拦截器 const onResponse = (response: any) => ({ ...response, intercepted: true }); const onResponseError = (error: any) => error; const responseInterceptorId = apiClient.addResponseInterceptor(onResponse, onResponseError); // 创建新实例 const newInstance = apiClient.createInstance({ timeout: 5000 }); // 验证新实例有拦截器方法 expect(typeof newInstance.addRequestInterceptor).toBe('function'); expect(typeof newInstance.addResponseInterceptor).toBe('function'); expect(typeof newInstance.removeRequestInterceptor).toBe('function'); expect(typeof newInstance.removeResponseInterceptor).toBe('function'); // 清理 apiClient.removeRequestInterceptor(requestInterceptorId); apiClient.removeResponseInterceptor(responseInterceptorId); }); it('应该能够处理headers类型转换', () => { // 设置原始实例的headers apiClient.setDefaults({ headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'custom-value', 'X-Number-Header': 123, 'X-Boolean-Header': true, 'X-Object-Header': { key: 'value' }, // 这个应该被过滤掉 'X-Function-Header': () => {} // 这个应该被过滤掉 } }); // 创建新实例 const newInstance = apiClient.createInstance({ timeout: 5000 }); // 验证新实例创建成功 expect(newInstance).toBeDefined(); }); it('应该能够创建嵌套实例', () => { // 创建第一层新实例 const firstLevelInstance = apiClient.createInstance({ timeout: 3000 }); // 从第一层实例创建第二层实例 const secondLevelInstance = firstLevelInstance.createInstance({ baseURL: 'https://api.example.com' }); // 验证第二层实例创建成功 expect(secondLevelInstance).toBeDefined(); expect(typeof secondLevelInstance.get).toBe('function'); expect(typeof secondLevelInstance.post).toBe('function'); }); it('应该能够处理空配置创建实例', () => { const newInstance = apiClient.createInstance(); expect(newInstance).toBeDefined(); expect(typeof newInstance.get).toBe('function'); expect(typeof newInstance.post).toBe('function'); }); it('应该能够处理没有headers的实例', () => { // 创建一个没有headers的实例 const noHeadersInstance = apiClient.createInstance({ timeout: 2000 }); // 从没有headers的实例创建新实例 const newInstance = noHeadersInstance.createInstance({ baseURL: 'https://test.api.com' }); expect(newInstance).toBeDefined(); }); }); describe('错误处理', () => { it('应该能够处理请求错误', async () => { const errorMessage = 'Request failed'; mockAxios.request.mockRejectedValue(createMockAxiosError(errorMessage)); await expect(apiClient.get('/test')).rejects.toThrow(errorMessage); }); it('应该能够处理网络错误', async () => { const networkError = new Error('Network Error'); mockAxios.request.mockRejectedValue(networkError); await expect(apiClient.get('/test')).rejects.toThrow('Network Error'); }); }); describe('类型转换', () => { it('应该正确处理请求配置转换', async () => { const config = createMockApiRequestConfig({ url: '/test', method: 'POST', data: { name: 'Test' }, headers: { 'Custom-Header': 'value' } }); const mockData = { success: true }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); await apiClient.request(config); // 验证axios被调用时接收到了正确的配置 expect(mockAxios.request).toHaveBeenCalled(); }); it('应该正确处理响应数据转换', async () => { const mockData = { id: 1, name: 'Test' }; mockAxios.request.mockResolvedValue(createMockAxiosResponse(mockData)); const result = await apiClient.get('/test'); // 验证返回的数据是正确的 expect(result).toEqual(mockData); }); }); });