diff --git a/test/exponential-backoff.test.ts b/test/exponential-backoff.test.ts index 23f30b3..75e781c 100644 --- a/test/exponential-backoff.test.ts +++ b/test/exponential-backoff.test.ts @@ -15,22 +15,22 @@ const testExponentialBackoffRunUsesDefaultOptions = async (): Promise => { try { // The wrapped function fails on its first invocation and succeeds on the second. // That forces ExponentialBackoff.run down the retry path using default options. - const fn = vi.fn().mockRejectedValueOnce(new Error('retry me')) + const rejectThenResolveFn = vi.fn().mockRejectedValueOnce(new Error('retry me')) .mockResolvedValueOnce('static-result'); // Call the static helper with no onError and no options — defaults apply entirely. - const promise = ExponentialBackoff.run(fn); + const promise = ExponentialBackoff.run(rejectThenResolveFn); // Yield one microtask so the first (immediate) attempt completes and schedules the retry timer. await Promise.resolve(); - expect(fn).toHaveBeenCalledTimes(1); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(1); // Default baseDelay is 1000ms; advancing less would not trigger the retry yet. await vi.advanceTimersByTimeAsync(1_000); // The retry should have succeeded and returned the resolved value from the mock. await expect(promise).resolves.toBe('static-result'); - expect(fn).toHaveBeenCalledTimes(2); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); vi.restoreAllMocks(); @@ -43,19 +43,19 @@ const testExponentialBackoffRunUsesDefaultOptions = async (): Promise => { */ const testExponentialBackoffRunWithPartialOptions = async (): Promise => { // Same fail-then-succeed pattern; we only care that partial options still enable a retry. - const fn = vi.fn().mockRejectedValueOnce(new Error('retry me')) + const rejectThenResolveFn = vi.fn().mockRejectedValueOnce(new Error('retry me')) .mockResolvedValueOnce('done'); // baseDelay/jitter of 0 skip real waiting; maxAttempts: 3 gives headroom for one retry. // onError is explicitly undefined to verify the default no-op handler is used. - const result = await ExponentialBackoff.run(fn, undefined, { + const result = await ExponentialBackoff.run(rejectThenResolveFn, undefined, { baseDelay: 0, jitter: 0, maxAttempts: 3, }); expect(result).toBe('done'); - expect(fn).toHaveBeenCalledTimes(2); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(2); }; /** @@ -63,16 +63,16 @@ const testExponentialBackoffRunWithPartialOptions = async (): Promise => { * the instance's stored options when no per-run options are supplied. */ const testExponentialBackoffInstanceRunUsesDefaultOnError = async (): Promise => { - const fn = vi.fn().mockRejectedValueOnce(new Error('retry me')) + const rejectThenResolveFn = vi.fn().mockRejectedValueOnce(new Error('retry me')) .mockResolvedValueOnce('instance-result'); // Options live on the instance; run(fn) should read them instead of static defaults. const backoff = new ExponentialBackoff({ baseDelay: 0, jitter: 0, maxAttempts: 3 }); - const result = await backoff.run(fn); + const result = await backoff.run(rejectThenResolveFn); expect(result).toBe('instance-result'); - expect(fn).toHaveBeenCalledTimes(2); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(2); }; /** @@ -80,16 +80,16 @@ const testExponentialBackoffInstanceRunUsesDefaultOnError = async (): Promise => { // Always resolves — never enters the catch/retry branch. - const fn = vi.fn(async () => 'success'); + const resolveFn = vi.fn(async () => 'success'); const onError = vi.fn(); - const result = await ExponentialBackoff.run(fn, onError, { + const result = await ExponentialBackoff.run(resolveFn, onError, { baseDelay: 0, jitter: 0, }); expect(result).toBe('success'); - expect(fn).toHaveBeenCalledOnce(); + expect(resolveFn).toHaveBeenCalledOnce(); expect(onError).not.toHaveBeenCalled(); }; @@ -98,19 +98,19 @@ const testExponentialBackoffSucceedsOnFirstAttempt = async (): Promise => */ const testExponentialBackoffRetriesUntilSuccess = async (): Promise => { // Three invocations: two rejections then a success on the third call. - const fn = vi.fn().mockRejectedValueOnce(new Error('attempt 1')) + const tripleRejectFn = vi.fn().mockRejectedValueOnce(new Error('attempt 1')) .mockRejectedValueOnce(new Error('attempt 2')) .mockResolvedValueOnce('success'); // maxAttempts: 5 is high enough that we stop because fn succeeded, not because we hit the cap. - const result = await ExponentialBackoff.run(fn, () => {}, { + const result = await ExponentialBackoff.run(tripleRejectFn, () => {}, { baseDelay: 0, jitter: 0, maxAttempts: 5, }); expect(result).toBe('success'); - expect(fn).toHaveBeenCalledTimes(3); + expect(tripleRejectFn).toHaveBeenCalledTimes(3); }; /** @@ -121,11 +121,11 @@ const testExponentialBackoffCallsOnErrorForEachFailure = async (): Promise const error = new Error('temporary failure'); // Always rejects with the same error — we will exhaust all attempts. - const fn = vi.fn().mockRejectedValue(error); + const rejectFn = vi.fn().mockRejectedValue(error); const onError = vi.fn(); // maxAttempts: 3 means three tries total, all of which will fail. - await expect(ExponentialBackoff.run(fn, onError, { + await expect(ExponentialBackoff.run(rejectFn, onError, { baseDelay: 0, jitter: 0, maxAttempts: 3, @@ -144,11 +144,11 @@ const testExponentialBackoffThrowsMaxRetriesHitErrorWhenExhausted = async (): Pr const lastError = new Error('last'); // Two distinct errors so we can prove both are collected, not just the last one. - const fn = vi.fn().mockRejectedValueOnce(firstError) + const doubleRejectFn = vi.fn().mockRejectedValueOnce(firstError) .mockRejectedValueOnce(lastError); try { - await ExponentialBackoff.run(fn, () => {}, { + await ExponentialBackoff.run(doubleRejectFn, () => {}, { baseDelay: 0, jitter: 0, maxAttempts: 2, @@ -159,7 +159,7 @@ const testExponentialBackoffThrowsMaxRetriesHitErrorWhenExhausted = async (): Pr expect((error as ExponentialBackoffMaxRetriesHitError).cause).toEqual([ firstError, lastError ]); } - expect(fn).toHaveBeenCalledTimes(2); + expect(doubleRejectFn).toHaveBeenCalledTimes(2); }; /** @@ -168,12 +168,12 @@ const testExponentialBackoffThrowsMaxRetriesHitErrorWhenExhausted = async (): Pr */ const testExponentialBackoffWrapsNonErrorThrows = async (): Promise => { // Reject with a plain string — not an Error subclass. - const fn = vi.fn().mockRejectedValue('not-an-error'); + const rejectedFn = vi.fn().mockRejectedValue('not-an-error'); const onError = vi.fn(); // Single attempt — we fail fast and inspect what onError received. try { - await ExponentialBackoff.run(fn, onError, { + await ExponentialBackoff.run(rejectedFn, onError, { baseDelay: 0, jitter: 0, maxAttempts: 1, @@ -196,7 +196,7 @@ const testExponentialBackoffWrapsNonErrorThrows = async (): Promise => { * as an alternative to the static helper. */ const testExponentialBackoffFromAndInstanceRun = async (): Promise => { - const fn = vi.fn(async () => 42); + const successfullyResolve = vi.fn(async () => 42); // from() is a convenience constructor; run() on the result should behave like the static path. const backoff = ExponentialBackoff.from({ @@ -204,10 +204,10 @@ const testExponentialBackoffFromAndInstanceRun = async (): Promise => { jitter: 0, }); - const result = await backoff.run(fn); + const result = await backoff.run(successfullyResolve); expect(result).toBe(42); - expect(fn).toHaveBeenCalledOnce(); + expect(successfullyResolve).toHaveBeenCalledOnce(); }; /** @@ -215,21 +215,21 @@ const testExponentialBackoffFromAndInstanceRun = async (): Promise => { */ const testExponentialBackoffRetriesIndefinitelyWhenMaxAttemptsIsZero = async (): Promise => { // Four invocations: three failures then success — would exceed a cap of 3 if one existed. - const fn = vi + const tripleRejectThenResolveFn = vi .fn() .mockRejectedValueOnce(new Error('attempt 1')) .mockRejectedValueOnce(new Error('attempt 2')) .mockRejectedValueOnce(new Error('attempt 3')) .mockResolvedValueOnce('eventually'); - const result = await ExponentialBackoff.run(fn, () => {}, { + const result = await ExponentialBackoff.run(tripleRejectThenResolveFn, () => {}, { baseDelay: 0, jitter: 0, maxAttempts: 0, }); expect(result).toBe('eventually'); - expect(fn).toHaveBeenCalledTimes(4); + expect(tripleRejectThenResolveFn).toHaveBeenCalledTimes(4); }; /** @@ -241,13 +241,13 @@ const testExponentialBackoffIncreasesDelayExponentially = async (): Promise {}, { + const promise = ExponentialBackoff.run(doubleRejectThenResolveFn, () => {}, { baseDelay: 100, growthRate: 2, jitter: 0, @@ -257,15 +257,15 @@ const testExponentialBackoffIncreasesDelayExponentially = async (): Promise => { vi.spyOn(Math, 'random').mockReturnValue(0.5); try { - const fn = vi + const doubleRejectThenResolveFn = vi .fn() .mockRejectedValueOnce(new Error('attempt 1')) .mockRejectedValueOnce(new Error('attempt 2')) .mockResolvedValueOnce('success'); - const promise = ExponentialBackoff.run(fn, () => {}, { + const promise = ExponentialBackoff.run(doubleRejectThenResolveFn, () => {}, { baseDelay: 1_000, growthRate: 4, jitter: 0, @@ -297,15 +297,15 @@ const testExponentialBackoffCapsDelayAtMaxDelay = async (): Promise => { }); await Promise.resolve(); - expect(fn).toHaveBeenCalledTimes(1); + expect(doubleRejectThenResolveFn).toHaveBeenCalledTimes(1); // attempt 0: 1000 * 4^0 = 1000ms, below the 2000ms cap. await vi.advanceTimersByTimeAsync(1_000); - expect(fn).toHaveBeenCalledTimes(2); + expect(doubleRejectThenResolveFn).toHaveBeenCalledTimes(2); // attempt 1: uncapped would be 4000ms but maxDelay clamps to 2000ms. await vi.advanceTimersByTimeAsync(2_000); - expect(fn).toHaveBeenCalledTimes(3); + expect(doubleRejectThenResolveFn).toHaveBeenCalledTimes(3); await expect(promise).resolves.toBe('success'); } finally { @@ -323,10 +323,10 @@ const testExponentialBackoffAppliesJitter = async (): Promise => { vi.spyOn(Math, 'random').mockReturnValue(1); try { - const fn = vi.fn().mockRejectedValueOnce(new Error('attempt 1')) + const rejectThenResolveFn = vi.fn().mockRejectedValueOnce(new Error('attempt 1')) .mockResolvedValueOnce('success'); - const promise = ExponentialBackoff.run(fn, () => {}, { + const promise = ExponentialBackoff.run(rejectThenResolveFn, () => {}, { baseDelay: 1_000, growthRate: 1, jitter: 0.1, @@ -335,14 +335,14 @@ const testExponentialBackoffAppliesJitter = async (): Promise => { }); await Promise.resolve(); - expect(fn).toHaveBeenCalledTimes(1); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(1); // Advancing 899ms is one ms short of the jittered delay; 900ms triggers the retry. await vi.advanceTimersByTimeAsync(899); - expect(fn).toHaveBeenCalledTimes(1); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(1); - expect(fn).toHaveBeenCalledTimes(2); + expect(rejectThenResolveFn).toHaveBeenCalledTimes(2); await expect(promise).resolves.toBe('success'); } finally {