Postboy Help

@artstesh/postboy-testing Reference

Reference page for @artstesh/postboy-testing (applies to v3.4.x; peers @artstesh/postboy ^3.4.1 — the 3.4.x and 3.5.x lines are supported). For the usage guide see Testing and Testing quick start.

BDD-style (Given / When / Then) toolkit: a recording mock over the real bus, fluent assertions, async waiters, and stubs. Named exports only, no default export. Exported from the package root: PostboyServiceMock, HistoryCollection, PostboyTestingSettings, MessageHistory, PostboyWorld, PostboyMessageStreamService, PostboyWorldVerifier, PostboyWaiterService, WaitOptions, PostboyGivenService, PostboyThenService.

PostboyWorld

class PostboyWorld { constructor(settings: PostboyTestingSettings = { strict: false }); get postboy(): PostboyServiceMock; // inject into the SUT get history(): MessageHistory; get given(): PostboyGivenService; get then(): PostboyThenService; get waiter(): PostboyWaiterService; get mocks(): PostboyMessageStreamService; get registry(): PostboyAbstractRegistrator; // namespaced registrator used internally dispose(): void; }

dispose() resets the history, unsubscribes the mocks, tears down the internal mock-namespace…/waiter-namespace… registrators, and disposes the bus — call it in afterEach; the world is not reusable afterwards. The constructor takes only PostboyTestingSettings — the world builds its own history and mock.

interface PostboyTestingSettings { strict: boolean }

strict: false (default): the mock auto-registers unseen message IDs with a dummy Subject and unseen executors with a null-returning function. strict: true: real-bus behavior — unregistered types throw; register them first (e.g. world.registry.recordSubject(Type)). See Testing world.

PostboyServiceMock

class PostboyServiceMock extends PostboyService { constructor(history: MessageHistory, settings: PostboyTestingSettings = { strict: false }); fire<T>(message: T): void; // records, then real fire exec<E extends PostboyExecutor<T>, T>(executor: E): T; // records executor, then real exec sub<T>(type: MessageType<T>): Observable<T>; // counts the subscription once<T>(type: MessageType<T>): Observable<T>; // counted as well fireCallback<T>(message: PostboyCallbackMessage<T>, action?: (e: T) => void): Observable<T>; // records + intercepts finish() isRegistered<T>(type: MessageType<T>): boolean; // whether the id is taken; re-registering would replace the subject }

Extends the real PostboyService — the whole bus works; recording is layered on top. Prefer building it via PostboyWorld (which supplies the shared history) over constructing it manually.

PostboyGivenService — stubbing

All methods chainable (return this). See Testing world:

callback<T extends PostboyCallbackMessage<R>>(type: MessageType<T>, result: R): PostboyGivenService; // finish(result) on every fire executor<R, T extends PostboyExecutor<R>>(type: MessageType<T>, result: R): PostboyGivenService; // executor returning `result` event<T extends PostboyGenericMessage>(message: T): PostboyGivenService; // replay subject for the type AND immediate fire

PostboyThenService — assertions

fired<T extends PostboyMessage>(type: MessageType<T>): PostboyFiredThen<T>; notFired<T extends PostboyMessage>(type: MessageType<T>): PostboyThenService; // throws if fired subscribed<T extends PostboyMessage>(type: MessageType<T>): PostboySubscribedThen<T>;

Builders (throw plain Error on failure; and() returns to the then service):

// PostboyFiredThen<T> .once(): PostboyFiredThen<T>; // exactly 1 fire .times(count: number): PostboyFiredThen<T>; // exact count .atLeast(count: number): PostboyFiredThen<T>; .with(predicate: (m: T) => boolean): PostboyFiredThen<T>; // any recorded message matches .last(predicate: (m: T) => boolean): PostboyFiredThen<T>; // most recent matches .first(predicate: (m: T) => boolean): PostboyFiredThen<T>; // earliest matches get value(): T; // last fired instance; throws if none and(): PostboyThenService; // PostboySubscribedThen<T>: .once(); .times(n); .atLeast(n); .and();

See Testing assertions.

PostboyWaiterService — async waits

Default timeout 1000 ms; rejections are descriptive Errors. See Testing async:

waitFor<T>(type: MessageType<T>, options?: WaitOptions<T>): Promise<T>; waitForMany<T>(type: MessageType<T>, count: number, options?: WaitManyOptions<T>): Promise<T[]>; waitForAny<T>(types: MessageType<any>[], options?: WaitOptions<any>): Promise<T>; waitForCallbackResult<T>(type: MessageType<T>, options?: WaitOptions<T>): Promise<R>; waitForNone<T>(type: MessageType<T>, options?: WaitSilenceOptions<T>): Promise<void>; delay(ms: number): Promise<void>; dispose(): void;
interface WaitOptions<T> { timeout?: number; where?: (message: T) => boolean; includeHistory?: boolean } interface WaitManyOptions<T> extends WaitOptions<T> { exact?: boolean } // not exported from root interface WaitSilenceOptions<T> { timeout?; where?; includeHistory?; timeoutMessage?: string } // not exported from root

Method

Semantics

waitFor

Resolves with the first future message matching where; includeHistory: true first scans recorded messages and resolves synchronously on a hit

waitForMany

Collects until count matches; without exact resolves early on the Nth match, with exact: true waits the full timeout and resolves only for exactly N (rejecting on under- and over-count)

waitForAny

Races several types; resolves with the first matching message of any of them

waitForCallbackResult

Resolves with the result of the next finish(...) of the callback type; includeHistory: true first scans recorded results, same opt-in as waitFor

waitForNone

Resolves after timeout ms of silence; rejects immediately on a matching fire (or a history hit with includeHistory); timeoutMessage overrides the rejection text

delay

Simple promise-based delay for orchestrating test steps

MessageHistory & HistoryCollection

class MessageHistory { messages<T extends PostboyMessage>(type: MessageType<T>): HistoryCollection<T>; callbackResults<T extends PostboyCallbackMessage<R>>(type: MessageType<T>): HistoryCollection<{ message: T; result: R }>; callbackResult$<T extends PostboyCallbackMessage<R>>(type: MessageType<T>): Observable<{ message: T; result: R }>; subs<T extends PostboyMessage>(type: MessageType<T>): number; reset(): void; // subscription count; clear all } class HistoryCollection<T> { add(item: T): void; hasItem(item: T): boolean; has(predicate: (i: T) => boolean): boolean; get last(): T | null; get first(): T | null; get all(): T[]; get length(): number; clear(): void; }

callbackResult$ is the hot stream of finish() results that waitForCallbackResult consumes. See Testing assertions.

PostboyWorldVerifier — boolean checks

class PostboyWorldVerifier { constructor(history: MessageHistory); fired<T extends PostboyMessage>(type: MessageType<T>, times: number = 0): boolean; subscribed<T extends PostboyMessage>(type: MessageType<T>, times: number = 0): boolean; }

Non-throwing counterparts of the then assertions for conditional test logic: true if fired/subscribed at all; exact count when times > 0. The verifier is a separate export — the world has no verifier getter; construct it over the world's history: new PostboyWorldVerifier(world.history).

PostboyMessageStreamService — low-level mocks

class PostboyMessageStreamService { constructor(postboy: PostboyServiceMock, registry: PostboyAbstractRegistrator); mockEvent<T extends PostboyMessage>(message: T): void; // recordReplay + immediate fire mockCallback<R, T extends PostboyCallbackMessage<R>>(type: MessageType<T>, action: (m: T) => R): void; mockExecute<R, T extends PostboyExecutor<R>>(type: MessageType<T>, action: (m: T) => R): void; dispose(): void; }

What given delegates to. Reach for it directly only when a constant result is not enough and you need a custom action function. See Testing recipes.

Not exported from the package root

PostboyFiredThen, PostboySubscribedThen, CallbackResultHistoryItem, PostboyCallbackResult, WaitManyOptions, WaitSilenceOptions, PostboyMessageStoreMock, PostboyMiddlewareServiceMock, PostboyNamespaceStoreMock — reachable only as inferred return/parameter types; do not import them.

07 September 2026