skip to content

As a tech lead adopting native-image tests, how do you decide between @DisabledInAotMode and refactoring, and what are the coverage trade-offs?

level: principalimportance: nice to knowfreq 15%

answer

  1. AOT tests = native fidelity, not speed
  2. each disable subtracts native coverage
  3. refactor to @TestConfiguration baked-in bean
  4. method-level to minimize surface (6.2)
  5. missing-hints failure != context-mutation, fix hints not disable

basics

~20 s

Disable 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 s

Native/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
java
// 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

for a junior

Understand disabling reduces native coverage and should be rare.

for a middle

Offer the @TestConfiguration-bean refactor as an AOT-compatible alternative to a mock override.

for a senior

Weigh disable vs refactor vs duplicate, and separate context-mutation failures from missing-hint failures.

for a principal

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

context