3.4 KiB
createInMemoryEventsBusHarness
(no submodules)
Purpose
The testing harness packages an {@link InMemoryEventsBus} with a
waitFor helper for tests that publish asynchronously and need to
assert on history once the publish has settled. It is the standard
shape downstream plugin tests will import to avoid hand-rolling a
poll-with-timeout loop for every assertion. Importing
@suaveplan/api-events-in-memory/testing returns a tightly-scoped
surface that won't leak production-only construction details into test
code.
Features
- Fresh bus per harness — call
createInMemoryEventsBusHarness()once per test,dispose()at teardown; no shared state across cases. waitFor(predicate, { timeoutMs, intervalMs })polls until the predicate holds or the timeout elapses. The default timeout (1 s) and interval (5 ms) work for the overwhelming majority of unit tests; override only when integrating with slower infra.- Fast path: if the predicate already holds at call time the function resolves on the next microtask without sleeping.
- Reject-on-timeout includes the requested
timeoutMsin the message so test failures are easy to triage. - All options forwarded verbatim to {@link createInMemoryEventsBus}, so the harness inherits the strict-throw history-overflow policy and the Zod-validated options boundary.
Usage: Basic
import { createInMemoryEventsBusHarness } from "@suaveplan/api-events-in-memory/testing";
const harness = createInMemoryEventsBusHarness();
const { bus, waitFor } = harness;
bus.on("user.signup", async () => {
await Promise.resolve();
bus.publish("welcome.email.queued", { to: "u@example.com" });
});
await bus.publishAsync("user.signup", { userId: "u1" });
await waitFor((b) => b.published("welcome.email.queued").length === 1);
console.log(bus.published("welcome.email.queued"));
harness.dispose();
Usage: Advanced
Tighten the deadline for a fast unit test, or extend it for an integration test that sits behind a real database:
import { createInMemoryEventsBusHarness } from "@suaveplan/api-events-in-memory/testing";
const harness = createInMemoryEventsBusHarness({ maxHistoryPerTopic: 50 });
try {
await harness.waitFor(
(bus) => bus.published("payment.captured").length >= 3,
{ timeoutMs: 5000, intervalMs: 25 }
);
} catch (error) {
console.error("payment.captured fan-out stalled:", error);
throw error;
} finally {
harness.dispose();
}
dispose() is idempotent — calling it twice in afterEach after a
mid-test failure is safe.
API Reference
createInMemoryEventsBusHarness(options?): InMemoryEventsBusHarness— factory.InMemoryEventsBusHarness.bus: InMemoryEventsBus— the wrapped bus.InMemoryEventsBusHarness.waitFor(predicate, options?): Promise<void>— poll-with-timeout.InMemoryEventsBusHarness.dispose(): void— tear down the bus.WaitForPredicate = (bus: InMemoryEventsBus) => boolean.WaitForOptions = { timeoutMs?: number; intervalMs?: number }.
Implementation Notes
waitFor uses setTimeout rather than setImmediate/queueMicrotask
because real tests need to interleave with timers managed by the
runtime; busy-looping on microtasks starves them. The harness owns
dispose rather than asking the caller to call bus.dispose()
directly so that future versions can add additional resources
(meter providers, span recorders) without breaking the test surface.