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

4.6 KiB

testing — createInMemoryBrokerHarness

(no submodules)

Purpose

This module exposes a small, opinionated test harness around the in-memory broker. It removes the boilerplate of wiring an observer subscription and a teardown helper, so most tests collapse to "build harness, publish, assert on received, dispose". The harness deliberately shares the production broker — there is no separate "fake" broker hiding underneath — which means every assertion exercises the real fan-out pipeline and every regression caught in tests is one that would also manifest in production single-host deployments.

The harness lives under ./testing (not the main entry) so consumer production bundles never pay for the helper machinery; only test bundles import this subpath.

Features

  • createInMemoryBrokerHarness(opts?) factory that returns a ready-to-use bundle of { broker, received, observerSubscription, publishOne, dispose }.
  • A pre-registered observer subscription on the harness's default topic ("test.events") plus an extensible extraTopics list so tests that probe the registry shape don't need to subscribe manually.
  • publishOne(payload, opts?) convenience that materialises a full BrokerEnvelope from a payload, defaulting topic and tenant from the harness configuration.
  • dispose() is idempotent and tolerant of partial teardown — it can be called from afterEach even if the test itself disposed the broker directly.

Usage: Basic

import { describe, expect, it } from "@suaveplan/testing/runner";
import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing";

describe("broker harness", () => {
    it("captures every published envelope", { timeout: 5000 }, async () => {
        const harness = createInMemoryBrokerHarness<{ id: number }>();
        try {
            await harness.publishOne({ id: 1 });
            await harness.publishOne({ id: 2 });
            expect(harness.received.map((e) => e.payload.id)).toEqual([1, 2]);
        } finally {
            harness.dispose();
        }
    });
});

Usage: Advanced

import { describe, expect, it } from "@suaveplan/testing/runner";
import type { TenantId } from "@suaveplan/api-types/tenant";
import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing";

describe("broker harness — await-all + multi-topic", () => {
    it("delivers across additional topics", { timeout: 5000 }, async () => {
        const harness = createInMemoryBrokerHarness<string>({
            broker: { deliveryMode: "await-all" },
            topic: "orders",
            tenant: "t1" as unknown as TenantId,
            extraTopics: ["audit", "billing"],
        });
        try {
            await harness.publishOne("ok", { topic: "orders" });
            // The probe subscribers on audit/billing keep them
            // registered for `subscriberCount` introspection.
            expect(harness.broker.subscriberCount("orders")).toBe(1);
            expect(harness.broker.subscriberCount("audit")).toBe(1);
            expect(harness.received).toHaveLength(1);
            expect(harness.received[0]?.payload).toBe("ok");
        } finally {
            harness.dispose();
        }
    });
});

API Reference

  • createInMemoryBrokerHarness<Payload>(opts?): InMemoryBrokerHarness<Payload>
  • interface CreateInMemoryBrokerHarnessOptions<Payload> — { broker?, topic?, tenant?, extraTopics?, initialPayload? }.
  • interface InMemoryBrokerHarness<Payload> — { broker, received, observerSubscription, publishOne, dispose }.

Implementation Notes

  • The harness is deliberately tiny — it intentionally does NOT attempt to drive the runWsEngineConformance harness, because that one expects a full (bus, registry, broker, makeConnection) tuple while this package only owns the broker leg. The package's conformance test in tests/conformance/ runs broker-shaped type assertions instead, which is the right scope for a broker-only package.
  • extraTopics register a no-op subscriber so the topic survives in the registry for introspection (subscriberCount(topic) === 1). Without a probe subscriber the in-memory broker reaps zero-size topic entries from its Map and subscriberCount would report 0, which is the correct production behaviour but inconvenient when a test wants to assert "this topic exists right now".
  • The harness wraps the observer unsubscribe and broker dispose in separate try/catch blocks so an out-of-order teardown (test disposes the broker, then afterEach calls harness.dispose()) does not surface stale BROKER_DISPOSED errors.