diff --git a/README.md b/README.md index 1fabea4..aafc6cf 100644 --- a/README.md +++ b/README.md @@ -270,20 +270,22 @@ await QueueManager.init({ locations: ['./app/jobs/**/*.ts'], }) -const adapter = QueueManager.fake() +// The `using` keyword automatically restores the real adapters when +// the variable goes out of scope (at the end of the test function). +using fake = QueueManager.fake() await SendEmailJob.dispatch({ to: 'user@example.com' }) -adapter.assertPushed(SendEmailJob) -adapter.assertPushed(SendEmailJob, { +fake.assertPushed(SendEmailJob) +fake.assertPushed(SendEmailJob, { queue: 'default', payload: (payload) => payload.to === 'user@example.com', }) -adapter.assertPushedCount(1) - -QueueManager.restore() +fake.assertPushedCount(1) ``` +You can also call `QueueManager.restore()` manually if you need more control over when the real adapters are restored. + ### Sync (for testing) ```typescript diff --git a/src/drivers/fake_adapter.ts b/src/drivers/fake_adapter.ts index 5ae278c..ddc528a 100644 --- a/src/drivers/fake_adapter.ts +++ b/src/drivers/fake_adapter.ts @@ -71,6 +71,19 @@ export class FakeAdapter implements Adapter { #pendingTimeouts = new Set() #schedules = new Map() #pushedJobs: FakeJobRecord[] = [] + #onDispose?: () => void + + /** + * Set the function to call when the fake is disposed + */ + onDispose(fn: () => void) { + this.#onDispose = fn + return this + } + + [Symbol.dispose]() { + this.#onDispose?.() + } setWorkerId(_workerId: string): void {} diff --git a/src/queue_manager.ts b/src/queue_manager.ts index 30e493e..76d4377 100644 --- a/src/queue_manager.ts +++ b/src/queue_manager.ts @@ -219,19 +219,18 @@ class QueueManagerSingleton { * Replace all adapters with a fake adapter for testing. * * The fake adapter records pushed jobs and exposes assertion helpers. - * Call `restore()` to return to the previous configuration. - * - * @returns The fake adapter instance for assertions - * @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called + * Use the `using` keyword to automatically restore the previous + * configuration when the variable goes out of scope, or call + * `restore()` manually. * * @example * ```typescript - * const fake = QueueManager.fake() + * using fake = QueueManager.fake() * * await SendEmailJob.dispatch({ to: 'user@example.com' }) * * fake.assertPushed(SendEmailJob) - * QueueManager.restore() + * // Automatically restored at end of scope * ``` */ fake(): FakeAdapter { @@ -244,6 +243,7 @@ class QueueManagerSingleton { } const fakeAdapter = new FakeAdapter() + fakeAdapter.onDispose(() => this.restore()) this.#fakeState = { defaultAdapter: this.#defaultAdapter, diff --git a/tests/queue_manager.spec.ts b/tests/queue_manager.spec.ts index 399728e..3bd53f8 100644 --- a/tests/queue_manager.spec.ts +++ b/tests/queue_manager.spec.ts @@ -168,6 +168,24 @@ test.group('QueueManager', () => { await QueueManager.destroy() }) + test('should restore fake using Symbol.dispose', async ({ assert }) => { + await QueueManager.init({ + default: 'sync', + adapters: { sync: sync() }, + }) + + const original = QueueManager.use() + + { + using fake = QueueManager.fake() + assert.strictEqual(QueueManager.use(), fake) + } + + assert.strictEqual(QueueManager.use(), original) + + await QueueManager.destroy() + }) + test('should destroy existing adapter instances before reinitializing', async ({ assert, cleanup,