Postboy Help

Assertions with `then`

world.then is the assertion API of the testing toolkit — a declarative, BDD‑style layer on top of the recorded message history.
It lets you express what should have happened in a way that reads like natural language.
If an expectation fails, it throws a clear, descriptive error pointing to the exact problem.

Access it after your action:

service.doSomething(); world.then.fired(MyEvent).once();

All then methods work synchronously — they inspect the history that has been recorded up to that point.
For asynchronous scenarios, use world.waiter first, then verify with then.

then.fired(type) – verify sent messages

then.fired(SomeMessage) returns a PostboyFiredThen<T> builder.
From there you can chain one or more verifications.

world.then.fired(OrderPlacedEvent) .once() .with(event => event.amount > 0);

Available methods on PostboyFiredThen<T>

  1. .once()

Asserts the message was sent exactly once.

world.then.fired(OrderPlacedEvent).once();
  1. .times(count: number)

Asserts the message was sent exactly count times.

world.then.fired(OrderPlacedEvent).times(3);
  1. .atLeast(count: number)

Asserts the message was sent at least count times.

world.then.fired(OrderPlacedEvent).atLeast(2);
  1. .with(predicate: (message: T) => boolean)

Searches through all recorded messages of this type and passes if at least one satisfies the predicate.

world.then.fired(OrderPlacedEvent) .with(event => event.orderId === 42);

You can chain multiple .with() calls to check different conditions on the same collection.

  1. .first(predicate: (message: T) => boolean)

Applies the predicate to the first message only.

world.then.fired(OrderPlacedEvent) .first(event => event.phase === 'START');

Throws if the history is empty or the predicate fails.

  1. .last(predicate: (message: T) => boolean)

Like .first(), but checks the most recent message.

world.then.fired(OrderPlacedEvent) .last(event => event.phase === 'FINISH');
  1. .value

Returns the last recorded message of this type.
Throws if no message was sent at all.

const lastEvent = world.then.fired(OrderPlacedEvent).value; expect(lastEvent.orderId).toBe(42);

Use .value when you want to run additional, custom assertions on the last message.

  1. .and()

Ends the chain for this message type and returns the parent PostboyThenService, enabling assertions on another message.

world.then .fired(FirstEvent).once() .and() .fired(SecondEvent).atLeast(1);

then.notFired(type) – verify absence

Asserts that no message of the given type was sent.
Throws immediately if any recording exists.

world.then.notFired(ErrorEvent);

This is perfect for negative testing — ensuring an error event was not emitted under normal conditions.

then.subscribed(type) – verify subscriptions

While then.fired checks what was emitted, then.subscribed checks what was listened to.
Every call to postboy.sub(type) or postboy.once(type) increments a counter.
then.subscribed(type) returns a PostboySubscribedThen<T> builder.

world.then.subscribed(OrderPlacedEvent).atLeast(1);

Available methods on PostboySubscribedThen<T>

  1. .once()

Asserts exactly one subscription.

  1. .times(count: number)

Asserts exactly count subscriptions.

  1. .atLeast(count: number)

Asserts at least count subscriptions.

  1. .and()

Returns the parent PostboyThenService for further assertions.

world.then .subscribed(OrderPlacedEvent).once() .and() .subscribed(InventoryUpdatedEvent).once();

Note: .with(), .first(), and .last() are not available for subscriptions — they are counters, not collections.

How then works under the hood

TesterTesterworld.thenworld.thenMessageHistoryMessageHistoryHistoryCollection<T>HistoryCollection<T>fired(MyEvent).once()messages(MyEvent)get or create collectionlength = Nlengthalt[N == 1]success (nothing thrown)[N != 1]throw AssertionError

then reads the same data as world.history, but wraps it in a fluent, error‑throwing interface. That’s why you can safely use it without manual try‑catch blocks — your test runner will report failures as standard test failures.

Chaining multiple verifications A typical test can chain several assertions in one statement:

world.then .fired(OrderPlacedEvent).once() .with(e => e.status === 'OK') .last(e => e.source === 'web') .and() .notFired(OrderFailedEvent) .and() .subscribed(OrderPlacedEvent).atLeast(2);

This reads top‑to‑bottom and mirrors your thinking: “OrderPlacedEvent was fired once, with certain properties, the last one came from the web, nothing failed, and there were at least two subscribers.”

Important details

  • All methods are synchronous — they examine the history as it exists right now.

  • .value returns the last message, not the first. Use .first() if you need the earliest one.

  • .with() scans all messages, so it passes if any message matches. Combine with .once() to ensure there’s exactly one such message.

  • Missing messages cause immediate errors — you don’t need a separate expect; the chain itself is the assertion.

  • then and history share the same data — you can mix them freely within a test.

Next steps Now that you can verify what happened, learn how to set up mock behaviour with given, or master async testing with waiter.

  • Given: mock setup – prepare events, callbacks, and executors.

  • Waiter: async testing – wait for messages, silence, or callback results.

  • Recipes – real‑world examples combining then, given, and waiter.

07 мая 2026