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

140 lines
4.6 KiB
JavaScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import useRetry from '../src/retry'
describe('useRetry', () => {
beforeEach(() => {
vi.useFakeTimers() // 使用假定时器
})
afterEach(() => {
vi.clearAllTimers() // 清除所有定时器
vi.useRealTimers() // 恢复真实定时器
})
it('should execute successfully on first try', async () => {
const mockFn = vi.fn().mockResolvedValue('success') // 模拟成功
const { run, loading, error } = useRetry(mockFn)
const promise = run()
expect(loading.value).toBe(true)
expect(error.value).toBe(null)
const result = await promise
expect(result).toBe('success')
expect(loading.value).toBe(false)
expect(error.value).toBe(null)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('should retry on failure and succeed eventually', async () => {
const mockError = new Error('test error')
const mockFn = vi
.fn()
.mockRejectedValueOnce(mockError)
.mockRejectedValueOnce(mockError)
.mockResolvedValue('success')
const { run, loading, error } = useRetry(mockFn, {
retries: 3,
delay: 1000,
})
const promise = run()
expect(loading.value).toBe(true)
expect(error.value).toBe(null)
// First failure
await vi.advanceTimersByTime(0)
expect(error.value).toBe(mockError)
expect(loading.value).toBe(true)
// Second failure
await vi.advanceTimersByTime(1000)
expect(error.value).toBe(mockError)
expect(loading.value).toBe(true)
// Success on third try
await vi.advanceTimersByTime(1000)
const result = await promise
expect(result).toBe('success')
expect(loading.value).toBe(false)
expect(error.value).toBe(null)
expect(mockFn).toHaveBeenCalledTimes(3)
})
it('should throw after max retries', async () => {
const mockError = new Error('persistent error')
const mockFn = vi.fn().mockRejectedValue(mockError)
const { run, loading, error } = useRetry(mockFn, {
retries: 2,
delay: 500,
})
const promise = run()
expect(loading.value).toBe(true)
expect(error.value).toBe(null)
// First attempt
await vi.advanceTimersByTime(0)
expect(error.value).toBe(mockError)
expect(loading.value).toBe(true)
// First retry
await vi.advanceTimersByTime(500)
expect(error.value).toBe(mockError)
expect(loading.value).toBe(true)
// Second retry
await vi.advanceTimersByTime(500)
await expect(promise).rejects.toThrow(mockError)
expect(loading.value).toBe(false)
expect(error.value).toBe(mockError)
expect(mockFn).toHaveBeenCalledTimes(3)
})
it('should use default options when not provided', async () => {
const mockError = new Error('temporary error')
const mockFn = vi
.fn()
.mockRejectedValueOnce(mockError)
.mockRejectedValueOnce(mockError)
.mockRejectedValueOnce(mockError)
.mockResolvedValue('success')
const { run, loading, error } = useRetry(mockFn)
const promise = run()
expect(loading.value).toBe(true)
// First attempt
await vi.advanceTimersByTime(0)
expect(error.value).toBe(mockError)
// First retry
await vi.advanceTimersByTime(1000)
expect(error.value).toBe(mockError)
// Second retry
await vi.advanceTimersByTime(1000)
expect(error.value).toBe(mockError)
// Third retry
await vi.advanceTimersByTime(1000)
const result = await promise
expect(result).toBe('success')
expect(loading.value).toBe(false)
expect(error.value).toBe(null)
expect(mockFn).toHaveBeenCalledTimes(4)
})
it('should reset error state on each run', async () => {
const mockError = new Error('first run error')
const mockFn = vi.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce('second run success')
const { run, error } = useRetry(mockFn, { retries: 0 })
// First run fails
await expect(run()).rejects.toThrow(mockError)
expect(error.value).toBe(mockError)
// Second run succeeds
const result = await run()
expect(result).toBe('second run success')
expect(error.value).toBe(null)
})
it('should prevent concurrent executions', async () => {
const mockFn = vi.fn(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return 'success'
})
const { run, loading } = useRetry(mockFn)
// First execution
const promise1 = run()
expect(loading.value).toBe(true)
// Second execution during first one
const promise2 = run()
// Should reuse the first execution
expect(mockFn).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTime(1000)
const [result1, result2] = await Promise.all([promise1, promise2])
expect(result1).toBe('success')
expect(result2).toBe('success')
expect(mockFn).toHaveBeenCalledTimes(1)
expect(loading.value).toBe(false)
})
})
//# sourceMappingURL=retry.spec.js.map