allinssl/frontend/packages/vue/hooks/test/axios.spec.js

430 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { nextTick, ref } from 'vue'
import { useAxios, MiddlewareStage } from '../src/axios'
import axios from 'axios'
// 模拟axios
vi.mock('axios', () => {
return {
default: {
create: vi.fn(() => ({
interceptors: {
request: {
use: vi.fn(),
eject: vi.fn(),
},
response: {
use: vi.fn(),
eject: vi.fn(),
},
},
request: vi.fn(),
})),
isCancel: vi.fn((error) => error && error.__CANCEL__),
CancelToken: {
source: vi.fn(() => ({
token: 'mock-token',
cancel: vi.fn(),
})),
},
},
}
})
describe('useAxios', () => {
let mockResponse
beforeEach(() => {
vi.clearAllMocks()
mockResponse = {
data: { message: 'success' },
status: 200,
statusText: 'OK',
headers: {},
config: {},
}
// 设置axios.request的模拟实现
axios.create().request.mockImplementation(() => Promise.resolve(mockResponse))
})
afterEach(() => {
vi.clearAllMocks()
})
it('应该返回正确的响应数据', async () => {
const { data, error, loading, request } = useAxios()
expect(loading.value).toBe(false)
expect(data.value).toBe(null)
expect(error.value).toBe(null)
const promise = request({ url: '/test' })
expect(loading.value).toBe(true)
await promise
expect(loading.value).toBe(false)
expect(data.value).toEqual({ message: 'success' })
expect(error.value).toBe(null)
expect(axios.create().request).toHaveBeenCalledWith(expect.objectContaining({ url: '/test' }))
})
it('当请求失败时应该设置错误信息', async () => {
const mockError = new Error('Request failed')
axios.create().request.mockImplementation(() => Promise.reject(mockError))
const { data, error, loading, request } = useAxios()
try {
await request({ url: '/test' })
} catch (e) {
// 预期抛出错误
}
expect(loading.value).toBe(false)
expect(data.value).toBe(null)
expect(error.value).toBe(mockError)
})
it('应该支持请求重试', async () => {
// 前两次请求失败,第三次成功
axios
.create()
.request.mockImplementationOnce(() => Promise.reject(new Error('Retry 1')))
.mockImplementationOnce(() => Promise.reject(new Error('Retry 2')))
.mockImplementationOnce(() => Promise.resolve(mockResponse))
const { data, request } = useAxios()
await request({
url: '/test',
retry: true,
retryTimes: 3,
})
expect(axios.create().request).toHaveBeenCalledTimes(3)
expect(data.value).toEqual({ message: 'success' })
})
it('应该能够取消请求', async () => {
// 模拟取消功能
const cancelError = new Error('Request canceled')
cancelError.__CANCEL__ = true
const sourceCancel = axios.CancelToken.source().cancel
sourceCancel.mockImplementation(() => {
axios.create().request.mockImplementation(() => Promise.reject(cancelError))
})
const { loading, request, cancel } = useAxios()
const requestPromise = request({ url: '/test', requestId: 'test-request' })
cancel('test-request')
try {
await requestPromise
} catch (e) {
// 预期抛出错误
}
expect(loading.value).toBe(false)
expect(sourceCancel).toHaveBeenCalled()
})
it('应该支持中间件机制', async () => {
const requestMiddleware = vi.fn()
const responseMiddleware = vi.fn()
const { request, use } = useAxios()
// 添加请求中间件
use({
id: 'request-middleware',
stage: MiddlewareStage.REQUEST,
handler: requestMiddleware,
})
// 添加响应中间件
use({
id: 'response-middleware',
stage: MiddlewareStage.RESPONSE,
handler: responseMiddleware,
})
await request({ url: '/test' })
expect(requestMiddleware).toHaveBeenCalled()
expect(responseMiddleware).toHaveBeenCalled()
})
it('应该缓存请求结果', async () => {
const { request, clearCache } = useAxios()
await request({ url: '/cached', cache: true })
await request({ url: '/cached', cache: true })
// 由于缓存实际axios请求应该只执行一次
expect(axios.create().request).toHaveBeenCalledTimes(1)
// 清除缓存后再次请求应该执行新的请求
clearCache()
await request({ url: '/cached', cache: true })
expect(axios.create().request).toHaveBeenCalledTimes(2)
})
it('应该支持自定义实例配置', async () => {
const customConfig = {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'Custom-Value',
},
}
const { request } = useAxios({
options: customConfig,
})
await request({ url: '/test' })
// 验证创建实例时使用了自定义配置
expect(axios.create).toHaveBeenCalledWith(customConfig)
})
it('应该支持请求级别的配置覆盖实例配置', async () => {
const { request } = useAxios({
options: {
baseURL: 'https://api.example.com',
timeout: 5000,
},
})
await request({
url: '/test',
timeout: 10000, // 覆盖实例的timeout
headers: {
'X-Request-Header': 'Request-Value',
},
})
// 验证请求参数包含覆盖的配置
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
url: '/test',
timeout: 10000,
headers: {
'X-Request-Header': 'Request-Value',
},
}),
)
})
it('应该支持删除中间件', async () => {
const requestMiddleware = vi.fn()
const { request, use, eject } = useAxios()
// 添加中间件
use({
id: 'test-middleware',
stage: MiddlewareStage.REQUEST,
handler: requestMiddleware,
})
// 删除中间件
eject('test-middleware')
await request({ url: '/test' })
// 由于中间件已被删除,不应该被调用
expect(requestMiddleware).not.toHaveBeenCalled()
})
it('应该支持请求前的数据转换', async () => {
const { request, use } = useAxios()
// 添加请求转换中间件
use({
id: 'transform-request',
stage: MiddlewareStage.REQUEST,
handler: (config) => {
if (config.data) {
config.data = { ...config.data, transformed: true }
}
return config
},
})
await request({
url: '/test',
method: 'post',
data: { original: true },
})
// 验证请求数据被转换
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
data: { original: true, transformed: true },
}),
)
})
it('应该支持响应数据转换', async () => {
const { data, request, use } = useAxios()
// 添加响应转换中间件
use({
id: 'transform-response',
stage: MiddlewareStage.RESPONSE,
handler: (response) => {
response.data = { ...response.data, transformed: true }
return response
},
})
await request({ url: '/test' })
// 验证响应数据被转换
expect(data.value).toEqual({
message: 'success',
transformed: true,
})
})
it('应该支持响应错误处理中间件', async () => {
const mockError = new Error('Request failed')
axios.create().request.mockImplementation(() => Promise.reject(mockError))
const errorHandler = vi.fn().mockImplementation(() => {
// 将错误转换为成功响应
return {
data: { recovered: true },
status: 200,
statusText: 'OK',
headers: {},
config: {},
}
})
const { data, error, request, use } = useAxios()
// 添加错误处理中间件
use({
id: 'error-handler',
stage: MiddlewareStage.ERROR,
handler: errorHandler,
})
await request({ url: '/test' })
// 验证错误被处理且转换为成功响应
expect(errorHandler).toHaveBeenCalled()
expect(data.value).toEqual({ recovered: true })
expect(error.value).toBe(null)
})
it('应该支持请求防抖', async () => {
vi.useFakeTimers()
const { request } = useAxios()
// 短时间内发起多个相同请求
request({ url: '/debounced', debounce: true, debounceTime: 100 })
request({ url: '/debounced', debounce: true, debounceTime: 100 })
request({ url: '/debounced', debounce: true, debounceTime: 100 })
// 前进100ms触发防抖后的请求
vi.advanceTimersByTime(100)
await Promise.resolve() // 等待微任务队列
// 仅发送一次请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
vi.useRealTimers()
})
it('应该支持请求节流', async () => {
vi.useFakeTimers()
const { request } = useAxios()
// 立即执行第一个请求
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 这些请求应该被忽略
request({ url: '/throttled', throttle: true, throttleTime: 100 })
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 验证立即执行了第一个请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
// 重置模拟
axios.create().request.mockClear()
// 前进100ms节流时间结束
vi.advanceTimersByTime(100)
// 再次发送请求
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 验证发送了新的请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
vi.useRealTimers()
})
it('应该支持同时处理多个并发请求', async () => {
const { loading, request } = useAxios()
// 发起多个请求
const promise1 = request({ url: '/request1' })
const promise2 = request({ url: '/request2' })
const promise3 = request({ url: '/request3' })
expect(loading.value).toBe(true)
// 等待所有请求完成
await Promise.all([promise1, promise2, promise3])
expect(loading.value).toBe(false)
expect(axios.create().request).toHaveBeenCalledTimes(3)
})
it('应该支持通过配置禁用全局loading状态', async () => {
const { loading, request } = useAxios({
options: {
useGlobalLoading: false,
},
})
// 初始状态
expect(loading.value).toBe(false)
// 发起请求
const promise = request({ url: '/test' })
// loading状态应该仍为false
expect(loading.value).toBe(false)
await promise
expect(loading.value).toBe(false)
})
it('应该支持通过URL参数进行请求', async () => {
const { request } = useAxios()
await request({
url: '/test',
params: {
id: 123,
filter: 'active',
sort: 'desc',
},
})
// 验证请求包含参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
url: '/test',
params: { id: 123, filter: 'active', sort: 'desc' },
}),
)
})
it('应该支持自定义请求头', async () => {
const { request } = useAxios()
await request({
url: '/test',
headers: {
Authorization: 'Bearer token123',
'Accept-Language': 'zh-CN',
},
})
// 验证请求包含自定义头
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
Authorization: 'Bearer token123',
'Accept-Language': 'zh-CN',
},
}),
)
})
it('应该支持不同的响应类型', async () => {
mockResponse.data = 'blob-data'
const { data, request } = useAxios()
await request({
url: '/download',
responseType: 'blob',
})
// 验证请求参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
responseType: 'blob',
}),
)
// 验证获取了正确的响应
expect(data.value).toBe('blob-data')
})
it('应该支持请求超时配置', async () => {
const mockTimeoutError = new Error('timeout')
mockTimeoutError.code = 'ECONNABORTED'
axios.create().request.mockImplementation(() => Promise.reject(mockTimeoutError))
const { error, request } = useAxios()
try {
await request({
url: '/test',
timeout: 1000,
})
} catch (e) {
// 预期抛出错误
}
// 验证请求参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
timeout: 1000,
}),
)
// 验证错误状态
expect(error.value).toBe(mockTimeoutError)
})
it('应该支持动态更新请求选项', async () => {
const defaultOptions = ref({
baseURL: 'https://api.example.com',
headers: {
'Content-Type': 'application/json',
},
})
const { request } = useAxios({
options: defaultOptions,
})
// 发起第一个请求
await request({ url: '/test1' })
// 验证使用了初始配置
expect(axios.create).toHaveBeenCalledWith(defaultOptions.value)
// 更新配置
defaultOptions.value = {
...defaultOptions.value,
baseURL: 'https://api2.example.com',
timeout: 3000,
}
await nextTick()
// 发起第二个请求
await request({ url: '/test2' })
// 验证使用了更新后的配置
expect(axios.create).toHaveBeenCalledWith(defaultOptions.value)
})
})
//# sourceMappingURL=axios.spec.js.map