fetch-timeout.test.ts
1 import test from 'node:test' 2 import assert from 'node:assert/strict' 3 4 import { fetchWithTimeout } from './fetch-timeout' 5 6 const originalFetch = global.fetch 7 const originalSetTimeout = global.setTimeout 8 const originalClearTimeout = global.clearTimeout 9 10 test.afterEach(() => { 11 global.fetch = originalFetch 12 global.setTimeout = originalSetTimeout 13 global.clearTimeout = originalClearTimeout 14 }) 15 16 test('fetchWithTimeout throws TimeoutError with a clear message on timeout', async () => { 17 global.setTimeout = (((callback: (...args: unknown[]) => void) => { 18 queueMicrotask(() => callback()) 19 return 1 as unknown as ReturnType<typeof setTimeout> 20 }) as typeof setTimeout) 21 global.clearTimeout = (() => {}) as typeof clearTimeout 22 global.fetch = (((_input: RequestInfo | URL, init?: RequestInit) => new Promise<Response>((_resolve, reject) => { 23 const onAbort = () => reject(init?.signal?.reason ?? new DOMException('Aborted', 'AbortError')) 24 if (init?.signal?.aborted) onAbort() 25 else init?.signal?.addEventListener('abort', onAbort, { once: true }) 26 })) as typeof fetch) 27 28 await assert.rejects( 29 () => fetchWithTimeout('/slow', {}, 5_000), 30 (err: unknown) => { 31 assert.ok(err instanceof Error) 32 assert.equal(err.name, 'TimeoutError') 33 assert.match(err.message, /5000ms/) 34 return true 35 }, 36 ) 37 }) 38 39 test('fetchWithTimeout preserves caller abort signals', async () => { 40 const controller = new AbortController() 41 const expectedError = new DOMException('Manual cancel', 'AbortError') 42 controller.abort(expectedError) 43 global.fetch = (((_input: RequestInfo | URL, init?: RequestInit) => { 44 return Promise.reject(init?.signal?.reason ?? expectedError) 45 }) as typeof fetch) 46 47 await assert.rejects( 48 () => fetchWithTimeout('/aborted', { signal: controller.signal }, 5_000), 49 (err: unknown) => { 50 assert.strictEqual(err, expectedError) 51 return true 52 }, 53 ) 54 })