As a tech lead adopting native-image tests, how do you decide between @DisabledInAotMode and refactoring, and what are the coverage trade-offs?
answer
- AOT tests = native fidelity, not speed
- each disable subtracts native coverage
- refactor to @TestConfiguration baked-in bean
- method-level to minimize surface (6.2)
- missing-hints failure != context-mutation, fix hints not disable
basics
~20 sDisable a test in AOT mode only when its setup truly cannot work in a frozen context (like a mock bean override). Prefer refactoring to a real test-double bean so the test still runs natively. Every @DisabledInAotMode reduces native-image test coverage, so treat it as a deliberate exception, not a default.
solid answer
~50 sNative/AOT testing exists to catch native-only failures (missing reflection/resource hints, unsupported dynamic behavior). Every @DisabledInAotMode removes a test from that safety net, so I treat it as a last resort. My rule: if the incompatibility is inherent to running against a frozen context — runtime bean overrides via @MockitoBean/@MockitoSpyBean, programmatic bean registration, dynamic context tweaks — and there's no clean alternative, annotate it. But first I try to refactor: express the test double as a @TestConfiguration bean the AOT context can bake in, or restructure so the behavior is verified without mutating the context. I'd track a metric of AOT-disabled tests, review each in code review, and keep the most business-critical native paths covered by AOT-compatible integration tests. On Spring 6.2 I favor method-level annotations to disable the minimum surface. The goal: real native coverage of production-shaped contexts, with disables confined to genuinely context-mutating tests.
code
java · 18 lines// AOT-friendly alternative to a runtime @MockitoBean override:
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(OrderServiceAotTests.StubConfig.class) // baked into the AOT context, no runtime mutation
class OrderServiceAotTests {
@TestConfiguration
static class StubConfig {
@Bean PaymentGateway paymentGateway() {
return order -> Receipt.ok(); // deterministic stub bean
}
}
// no @DisabledInAotMode needed: this context is AOT-compatible
}go deeper
Understand disabling reduces native coverage and should be rare.
Offer the @TestConfiguration-bean refactor as an AOT-compatible alternative to a mock override.
Weigh disable vs refactor vs duplicate, and separate context-mutation failures from missing-hint failures.
Own governance: metrics, code-review gating, ensuring critical production contexts stay AOT-covered while confining disables to genuinely un-AOT-able tests.
## The strategic picture Running tests in **AOT mode / native image** is not about speed — it's about **fidelity**: it exercises the same reflection-free, frozen `ApplicationContext` your production native binary uses, catching **native-only defects** (missing `RuntimeHints` for reflection/resources/proxies, unsupported runtime dynamism). Each `@DisabledInAotMode` **subtracts** a test from that signal. So the lead's job is to keep the annotation rare and intentional. ## Decision framework 1. **Is the incompatibility inherent?** A frozen context cannot accept runtime bean **mutation**: `@MockitoBean`, `@MockitoSpyBean`, Boot's `@MockBean`/`@SpyBean`, programmatic `registerBean`, or `ApplicationContextInitializer`s that inject beans. If the test's essence is such mutation, it genuinely can't run under AOT. 2. **Can it be refactored to stay AOT-compatible?** Often yes: - Replace a runtime override with a **`@TestConfiguration`** that defines the fake/stub as an ordinary bean — the AOT generator bakes it into the generated context, no runtime mutation needed. - Split a mixed test so the AOT-safe assertions run natively and only the mutation-dependent slice is disabled (method-level on Spring 6.2). - Move pure-logic assertions to **plain unit tests** (no Spring context → AOT-irrelevant). 3. **If neither**, apply `@DisabledInAotMode` with a clear `value()` reason (6.2) so reviewers understand the exception. ## Coverage trade-offs - **Disabling** is cheap now but blinds you to native regressions in that path. - **Refactoring to a baked-in test double** preserves native coverage but changes test semantics (you assert against a stub bean, not a runtime mock; no per-test re-stubbing without extra design). - **Duplicating** (one mock-based JVM test + one AOT-compatible integration test of the same behavior) maximizes coverage at the cost of maintenance. ## Governance - Keep the **count of AOT-disabled tests** visible; gate growth in code review. - Prefer **class-level** when the whole context is un-AOT-able; **method-level** to shave the minimum when only part is. - Ensure your **critical production contexts** (security, persistence, web) are exercised by AOT-eligible tests so native hint gaps surface in CI, not in prod. ## Common gotchas - A green plain-JVM suite says nothing about native readiness; you must actually run the AOT/native job. - Disabling too much yields a native binary whose context is barely tested — the exact scenario native testing was meant to prevent. - `@DisabledInAotMode` doesn't provide missing reflection hints; if a test fails for **missing hints** (not context mutation), the fix is `@RegisterReflectionForBinding`/`RuntimeHintsRegistrar`, not disabling.
- A native test fails not because of context mutation but because a class is reflected on without a hint. Is @DisabledInAotMode the right fix?No. That is a missing-hint problem; register the hints (e.g., @RegisterReflectionForBinding or a RuntimeHintsRegistrar). Disabling would hide a real native gap you want covered.
- What metric would you track to keep AOT-disabling under control?The count (and trend) of @DisabledInAotMode occurrences and the share of critical production contexts covered by AOT-eligible tests; review each new disable in code review with a documented reason.
saying these in an interview costs you the question
- Sprinkling @DisabledInAotMode broadly to make the native build pass
- Using it to hide missing reflection/resource hints instead of registering them
- Assuming a green JVM suite proves native readiness
- Believing refactoring to a @TestConfiguration bean is impossible for mock scenarios