diff --git a/jest.config.js b/jest.config.js index 86f88fb..ffacc85 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,4 +8,4 @@ module.exports = { transform: { ...tsJestTransformCfg, }, -}; \ No newline at end of file +}; diff --git a/src/task-queue.test.ts b/src/task-queue.test.ts deleted file mode 100644 index 722212f..0000000 --- a/src/task-queue.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AsyncTaskQueue } from "./index"; - -test("example evaluates correctly", async () => { - const q = new AsyncTaskQueue( - async () => {/* no-op */}, - ) - // Handler will receive up to 4 items per batch. - .batchSize(4) - // Handler will be called at most once per 100 milliseconds. - .throttleMs(100); - - // Add items to the queue. - for (let n = 0; n < 1000; n++) { - q.push(n); - } - - await new Promise((resolve) => setTimeout(resolve, 4000)); - - expect(q.size()).toBeLessThan(900); - expect(q.size()).toBeGreaterThan(800); - - q.drain(); - expect(q.size()).toEqual(0); -}); diff --git a/src/retries.test.ts b/test/retries.test.ts similarity index 89% rename from src/retries.test.ts rename to test/retries.test.ts index 3e4fcb0..e9a67da 100644 --- a/src/retries.test.ts +++ b/test/retries.test.ts @@ -1,4 +1,4 @@ -import { withRetry } from "./retries"; +import { withRetry } from "../src/retries"; const FAST_RETRY_CONFIG: Parameters[1] = { attempts: 6, @@ -42,6 +42,8 @@ test("parameters are passed through", async () => { }); test("retries back off with expected delays", async () => { + // Don't enable the `advanceTimers` configuration or else risk skewing the + // timing scheme used in this test. jest.useFakeTimers(); let n = 0; @@ -93,7 +95,8 @@ test("retries back off with expected delays", async () => { await expect(promise).resolves.toEqual("success!") - // Fake timers don't play nicely with the `expect().rejects` API, so disable - // them again for other tests. + // Mock timers without auto-advance enabled are a pain to manage when merely + // counting retries, so use real timers and a very short backoff duration in + // other tests instead. jest.useRealTimers(); }); diff --git a/test/task-queue.test.ts b/test/task-queue.test.ts new file mode 100644 index 0000000..e9c742a --- /dev/null +++ b/test/task-queue.test.ts @@ -0,0 +1,42 @@ +import { AsyncTaskQueue } from "../src/index"; + +test("example evaluates correctly", async () => { + jest.useFakeTimers(); + + const THROTTLE_DELAY_MS = 100; + + const q = new AsyncTaskQueue( + async () => {/* no-op */ }, + ) + // Handler will receive up to 4 items per batch. + .batchSize(4) + // Handler will be called at most once per 100 milliseconds. + .throttleMs(THROTTLE_DELAY_MS); + + // Add items to the queue. + for (let n = 0; n < 1000; n++) { + q.push(n); + } + + // Advance the clock incrementally and asynchronously so that we yield to the + // task queue control loop frequently. This should allow it to complete its + // work more or less as it would with real timers. + // + // Advance timers by a shorter duration than THROTTLE_DELAY_MS to test that + // the task queue throttles correctly according to wall time and not merely + // according to event loop ticks. + const fakeTimerIncrementMs = Math.ceil(THROTTLE_DELAY_MS / 2); + for ( + let mockDelayMs = 0; + mockDelayMs < 4000; + mockDelayMs += fakeTimerIncrementMs + ) { + await jest.advanceTimersByTimeAsync(fakeTimerIncrementMs); + } + + expect(q.size()).toBeLessThan(850); + expect(q.size()).toBeGreaterThan(825); + + q.drain(); + expect(q.size()).toEqual(0); +});