mirror of https://github.com/allinssl/allinssl
430 lines
12 KiB
JavaScript
430 lines
12 KiB
JavaScript
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
|