skip to content

How do you assert which application events were published during a module test using PublishedEvents / AssertablePublishedEvents?

level: seniorimportance: should knowfreq 38%

answer

  1. inject PublishedEvents / AssertablePublishedEvents
  2. eventsOfType(...).matching(mapper, value)
  3. assertThat(events).contains(Type.class).matching(...)
  4. inspects recorded events — does NOT wait
  5. combine with Scenario for async: wait then assert

basics

~10 s

Inject PublishedEvents (or AssertablePublishedEvents) into an @ApplicationModuleTest. After exercising code, query it: eventsOfType(X.class).matching(...) to filter, and with AssertablePublishedEvents use assertThat(events).contains(X.class)... to fluently verify the right events were published.

solid answer

~40 s

Spring Modulith captures every application event published within an @ApplicationModuleTest so you can assert on them. Inject PublishedEvents as a test parameter and query it: publishedEvents.eventsOfType(OrderPlaced.class).matching(OrderPlaced::orderId, id) returns the matching events to assert on (count, contents). For a fluent AssertJ style, inject AssertablePublishedEvents and write assertThat(events).contains(OrderPlaced.class).matching(...). This is best for verifying synchronous or already-completed publications — 'did placing an order publish exactly one OrderPlaced with this id?'. It differs from Scenario, which actively waits for asynchronous outcomes; PublishedEvents inspects what has been recorded so far. For async flows you typically still use Scenario to wait, then PublishedEvents/AssertablePublishedEvents to make precise assertions about the full set of events emitted.

code

java · 22 lines
java
@ApplicationModuleTest
class OrderEventTests {

    @Autowired OrderService orders;

    @Test
    void publishesExactlyOneOrderPlaced(AssertablePublishedEvents events) {
        var order = orders.place(sampleOrder());

        assertThat(events)
            .contains(OrderPlaced.class)
            .matching(OrderPlaced::orderId, order.id());
    }

    @Test
    void queryStyle(PublishedEvents events) {
        orders.place(sampleOrder());

        var placed = events.eventsOfType(OrderPlaced.class);
        assertThat(placed).hasSize(1);
    }
}

go deeper

for a junior

Know you can inject PublishedEvents to check that an event was published in a test.

for a middle

Use eventsOfType + matching and the AssertablePublishedEvents fluent form correctly.

for a senior

Understand it inspects (doesn't wait), pair it with Scenario for async, and know the after-commit/transaction pitfall.

for a principal

Treat emitted integration events as a tested module contract; standardise assertion patterns so cross-module event coupling is verified, not assumed.

## The capability Within an `@ApplicationModuleTest`, Spring Modulith records all Spring **application events** published through the `ApplicationEventPublisher` during the test. Two injectable types expose them: - **`PublishedEvents`** — a queryable collection of recorded events. - **`AssertablePublishedEvents`** — the same, plus an AssertJ-flavored fluent assertion entry point. You declare either as a **test-method parameter** (or field) and Modulith populates it. ## PublishedEvents — querying ```java @Test void publishesOrderPlaced(PublishedEvents events) { orders.place(order); var matches = events.eventsOfType(OrderPlaced.class) .matching(OrderPlaced::orderId, order.id()); assertThat(matches).hasSize(1); } ``` Key methods on the filtered view (`PublishedEvents.TypedPublishedEvents<T>`): - `eventsOfType(Class<T>)` — narrow to events of a type. - `matching(Predicate<T>)` — filter by predicate. - `matching(Function<T,V>, V value)` — filter where a mapped value equals `value`. - It is `Iterable`, so AssertJ's `assertThat(...)` collection assertions work directly. ## AssertablePublishedEvents — fluent assertions ```java @Test void publishesOrderPlaced(AssertablePublishedEvents events) { orders.place(order); assertThat(events) .contains(OrderPlaced.class) .matching(OrderPlaced::orderId, order.id()); } ``` Here `assertThat(events)` returns a `PublishedEventsAssert` supporting `.contains(Type.class)` and `.matching(...)` chaining for readable, type-safe checks. ## PublishedEvents vs Scenario - **`PublishedEvents`/`AssertablePublishedEvents`**: *inspect what has already been recorded*. Great for **synchronous** publications, or as the precise assertion step **after** an async wait. They do **not** wait. - **`Scenario`**: *actively waits* (Awaitility) for an event or state change to occur — the tool for **asynchronous** choreography. A common combined pattern: use `Scenario` to `andWaitForStateChange(...)` (ensuring the async work finished), then use `AssertablePublishedEvents` to assert the full, exact set of events emitted along the way. ## Gotchas - **They don't wait.** Asserting on `PublishedEvents` immediately after triggering an `@Async`/after-commit flow can see *zero* events because the publication hasn't happened yet — use `Scenario` to await first. - **Scope of capture**: events must be published through the Spring `ApplicationEventPublisher` within the test's context to be recorded. - **After-commit events** are only published once the surrounding transaction commits; if your test never commits (e.g. rolled back), `@TransactionalEventListener(AFTER_COMMIT)` publications won't fire — a frequent source of 'my event wasn't recorded'. - Prefer `matching(mapper, value)` over asserting on raw `toString()` to keep tests robust. ## When to use Use these to make **precise, declarative** assertions about *which* events a module emits as part of its contract — a natural fit for verifying that a use case publishes exactly the integration events other modules rely on.

  • You assert on PublishedEvents right after triggering an @ApplicationModuleListener flow and see no events. Why?
    @ApplicationModuleListener is async + after-commit. Immediately after the trigger the async publication/handling hasn't run (and after-commit events need the tx to commit). Use Scenario to await the outcome first, then assert on the recorded events.
  • What's the difference between PublishedEvents and AssertablePublishedEvents?
    PublishedEvents is a queryable Iterable you assert on with standard AssertJ; AssertablePublishedEvents adds a fluent assertThat(events).contains(Type.class).matching(...) DSL. Same captured data, different assertion ergonomics.

saying these in an interview costs you the question

  • Thinking PublishedEvents waits for async events like Scenario does
  • Forgetting that AFTER_COMMIT events don't fire in a rolled-back test transaction
  • Asserting on event toString() instead of typed matching
  • Believing you must manually register a listener to capture events

context