What is @ApplicationModuleTest and how does it differ from @SpringBootTest? Explain its bootstrap modes.
answer
- slice test for ONE module, not whole app
- target inferred from test package
- BootstrapMode: STANDALONE / DIRECT_DEPENDENCIES / ALL_DEPENDENCIES
- unlocks Scenario + PublishedEvents injection
- 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 linespackage 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
Know it's a per-module Spring test that's lighter than @SpringBootTest.
Explain the three BootstrapModes precisely and that excluded modules' beans must be mocked; know the target comes from the package.
Use STANDALONE deliberately to surface boundary violations, choose bootstrap mode by test intent, and leverage Scenario/PublishedEvents injection.
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)