skip to content

Moments & Module Testing

Moments turn the passage of time into events you can react to, and @ApplicationModuleTest boots one module in isolation with a scenario API. The testing story is what makes modular boundaries worth declaring.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

6

What is @ApplicationModuleTest and how does it differ from @SpringBootTest? Explain its bootstrap modes.

level: middleimportance: must knowfreq 55%

answer

  1. slice test for ONE module, not whole app
  2. target inferred from test package
  3. BootstrapMode: STANDALONE / DIRECT_DEPENDENCIES / ALL_DEPENDENCIES
  4. unlocks Scenario + PublishedEvents injection
  5. STANDALONE surfaces hidden cross-module coupling

basics

~20 s

@ApplicationModuleTest is a Spring Modulith slice test that bootstraps only one module (plus chosen dependencies) instead of the whole app. Its BootstrapMode is STANDALONE (module only), DIRECT_DEPENDENCIES, or ALL_DEPENDENCIES, giving faster, isolated per-module integration tests.

solid answer

~40 s

@ApplicationModuleTest is Spring Modulith's integration-test annotation that boots a single application module in isolation rather than the entire application like @SpringBootTest. It is auto-detected from the test's package, so placing the test in a module's package targets that module. The BootstrapMode enum controls how much context loads: STANDALONE (default) runs only the module under test — other modules are absent; DIRECT_DEPENDENCIES also loads modules this one directly depends on; ALL_DEPENDENCIES loads the full transitive dependency tree. Beans from excluded modules are typically mocked (e.g. with @MockitoBean). Benefits: faster context, real module-boundary enforcement, and it enables injecting the Scenario API for event-driven testing plus PublishedEvents assertions. It's the 'slice test' for a modular monolith — narrower than @SpringBootTest, broader than a unit test.

code

java · 21 lines
java
package com.example.orders;

import org.springframework.modulith.test.ApplicationModuleTest;
import org.springframework.modulith.test.ApplicationModuleTest.BootstrapMode;

// Target module = com.example.orders (inferred from package)
@ApplicationModuleTest(mode = BootstrapMode.DIRECT_DEPENDENCIES)
class OrderModuleTests {

    @Autowired OrderService orders;

    // Beans from modules NOT loaded must be mocked
    @MockitoBean InventoryApi inventory;

    @Test
    void placesOrder(Scenario scenario) {           // Scenario injected by Modulith
        scenario.stimulate(() -> orders.place(sampleOrder()))
                .andWaitForEventOfType(OrderPlaced.class)
                .toArriveAndVerify(evt -> assertThat(evt.orderId()).isNotNull());
    }
}

go deeper

for a junior

Know it's a per-module Spring test that's lighter than @SpringBootTest.

for a middle

Explain the three BootstrapModes precisely and that excluded modules' beans must be mocked; know the target comes from the package.

for a senior

Use STANDALONE deliberately to surface boundary violations, choose bootstrap mode by test intent, and leverage Scenario/PublishedEvents injection.

for a principal

Set a team convention for module test isolation (default STANDALONE, mock at Api surfaces) so tests enforce architecture, and reserve @SpringBootTest for true e2e.

## What it is `@ApplicationModuleTest` (from `org.springframework.modulith.test`) is Spring Modulith's dedicated **integration test slice** for a single *application module*. Where `@SpringBootTest` starts the **entire** application context, `@ApplicationModuleTest` boots **just one module** (and optionally its dependencies), giving a smaller, faster, more focused context that still exercises real Spring wiring, persistence, and events. Under the hood it is meta-annotated with `@SpringBootTest` + a custom `@BootstrapWith` that limits component scanning and auto-configuration to the target module. ## How the target module is chosen The module under test is **inferred from the test class's package**. If your test lives in `com.example.orders`, that package is the `orders` module and it becomes the target. You don't name it explicitly (though you can via the `module` attribute). ## Bootstrap modes (`BootstrapMode`) The `mode` attribute selects how much of the module graph loads: 1. **`STANDALONE`** (default) — only the target module is bootstrapped. Beans it needs from *other* modules are **not** present; you provide them as test doubles (e.g. `@MockitoBean`). Fastest and most isolated; verifies the module works against mocked collaborators. 2. **`DIRECT_DEPENDENCIES`** — the target module **plus the modules it directly declares as dependencies** are bootstrapped with real beans. Transitive dependencies of those are still excluded. 3. **`ALL_DEPENDENCIES`** — the target module and its **entire transitive dependency tree** load with real beans. Closest to a full boot but still excludes unrelated modules. ```java @ApplicationModuleTest(mode = BootstrapMode.DIRECT_DEPENDENCIES) class OrderModuleTests { ... } ``` ## What you can inject Because it is a Modulith test, it unlocks Modulith-specific test support as method/field parameters: - **`Scenario`** — fluent, Awaitility-backed API to stimulate an action and wait for an event or state change (ideal for async, event-driven flows). - **`PublishedEvents` / `AssertablePublishedEvents`** — assert which application events were published during the test. Standard Spring Boot test facilities still apply: `@Autowired`, `@MockitoBean` (formerly `@MockBean`), `@Sql`, Testcontainers, etc. ## vs @SpringBootTest and vs unit tests - **Unit test**: no Spring context; pure objects and mocks. Fastest, narrowest. - **@ApplicationModuleTest**: real Spring context but **only one module** (± deps). Verifies wiring, persistence, transactions, and event flows for that module while honoring boundaries. - **@SpringBootTest**: whole application. Broadest and slowest; use for true end-to-end/cross-cutting tests. ## Why it matters for modular monoliths It lets each module be tested like an independently deployable service: fast context, no accidental reliance on beans that (per module boundaries) shouldn't be visible, and first-class support for the async event choreography that connects modules. STANDALONE also **surfaces boundary violations**: if your module secretly needs a bean from a module it doesn't declare a dependency on, the STANDALONE test fails to wire — exposing the hidden coupling. ## Gotchas - STANDALONE excludes other modules' beans, so you must mock cross-module collaborators or bump the bootstrap mode. - The target is package-driven; a misplaced test class targets the wrong module. - It still starts a real context, so it's heavier than a plain unit test — don't use it for pure logic. - Property/auto-config that other modules provide may be absent in STANDALONE; provide via `@TestPropertySource` or move up a bootstrap mode.

  • You use STANDALONE but the context fails because a bean from another module can't be found. What does that tell you and how do you fix it?
    Either the module legitimately depends on that collaborator (mock it with @MockitoBean or switch to DIRECT_DEPENDENCIES), or it reveals a hidden coupling to a module you didn't declare as a dependency — a boundary smell to fix in the design.
  • How is the module under test selected?
    By the test class's package: the package maps to an application module and that becomes the target. You can override it with the annotation's module/extraIncludes attributes if needed.
  • When would you still reach for @SpringBootTest instead?
    For genuinely cross-cutting or end-to-end scenarios spanning many modules, full web-layer/security integration, or verifying the whole application boots — cases where isolating a single module misses the point.

saying these in an interview costs you the question

  • Saying it boots the whole application like @SpringBootTest
  • Not knowing the module is inferred from the package
  • Thinking STANDALONE loads direct dependencies (it loads none)
  • Believing ALL_DEPENDENCIES loads every module in the app (only the transitive dependency tree of the target)

context

open as a page

Explain the Spring Modulith Scenario API for testing asynchronous inter-module event flows.

level: seniorimportance: must knowfreq 45%

basics

~20 s

Scenario is a fluent, Awaitility-backed test API (injected into @ApplicationModuleTest methods) that stimulates an action or publishes an event, then waits for an expected event to arrive or a state change to occur before verifying — removing flaky sleeps in async, event-driven tests.

open as a page

What is the Spring Modulith Moments component and which time-passage events does it publish?

level: juniorimportance: should knowfreq 30%

basics

~10 s

Moments is a Spring Modulith helper (spring-modulith-moments) that publishes application events as time passes: HourHasPassed, DayHasPassed, WeekHasPassed, MonthHasPassed, QuarterHasPassed and YearHasPassed. Modules subscribe with @EventListener to run scheduled-style logic.

open as a page

How do you deterministically test time-triggered logic with the Spring Modulith TimeMachine?

level: middleimportance: should knowfreq 35%

basics

~10 s

Enable the TimeMachine (spring.modulith.moments.enable-time-machine=true), inject it into the test, and call shiftBy(Duration) to fast-forward. Shifting past a boundary synchronously publishes the corresponding Moments events, so you can assert the reaction immediately.

open as a page

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

level: seniorimportance: should knowfreq 38%

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.

open as a page

When would you choose Spring Modulith Moments in production versus a real scheduler, and what distributed-deployment concerns arise?

level: principalimportance: nice to knowfreq 20%

basics

~10 s

Use Moments for coarse, in-process, module-local reactions to elapsed time (daily/monthly rollups) where testability via TimeMachine matters. Use Quartz/cron/ShedLock when you need cron precision, missed-fire recovery, or single-firing across a clustered deployment.

open as a page