Files
skills/docs/api-stack/api/api-events-in-memory/src/testing/testing.md
2026-05-29 08:20:56 +01:00

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 timeoutMs in 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.