skip to content

AOT & Native Testing

Testing under AOT and native constraints: AOT-optimized test contexts, running the suite as a native image, disabling incompatible tests, and asserting on hints. Interviewers ask how you gain confidence that the native build behaves like the JVM one.

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

explore

questions

18

Why are tests using @MockitoBean / @MockBean typically incompatible with AOT mode, and how does @DisabledInAotMode help?

level: middleimportance: must knowfreq 45%

answer

  1. mock override = runtime context mutation
  2. AOT context frozen at build time
  3. process-test-aot refresh fails
  4. @MockitoBean/@MockitoSpyBean/@MockBean
  5. skip class + disable at runtime

basics

~20 s

@MockitoBean and @MockBean replace a real bean with a mock by changing the application context. An AOT-built context is frozen and cannot be changed, so the mock cannot be installed. @DisabledInAotMode skips such tests when running in AOT mode.

solid answer

~50 s

Mock-based bean overrides (@MockitoBean, @MockitoSpyBean, or Spring Boot's older @MockBean/@SpyBean) work by mutating the ApplicationContext at test setup: they register or replace a bean definition so autowiring receives the mock. This is done through a context customizer / BeanFactoryPostProcessor that runs while the context is being built. Under AOT, the context is generated and frozen at build time — its bean definitions are fixed and those runtime customizers don't reshape it the same way, so the mock can't be substituted. During build-time test AOT processing, refreshing such a context also tends to fail. @DisabledInAotMode resolves both: Spring skips the class during AOT artifact generation, and at runtime (spring.aot.enabled=true) it disables the test rather than letting it fail or run against the real bean. So you annotate mock-heavy integration tests and keep the rest of the suite AOT-eligible.

code

java · 23 lines
java
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.aot.DisabledInAotMode;
import org.junit.jupiter.api.Test;
import static org.mockito.BDDMockito.given;
import static org.mockito.ArgumentMatchers.any;

@SpringBootTest
@DisabledInAotMode("@MockitoBean mutates the context; unsupported in a frozen AOT context")
class OrderServiceTests {

    @MockitoBean
    PaymentGateway paymentGateway; // replaces the real bean -> not AOT-compatible

    @org.springframework.beans.factory.annotation.Autowired
    OrderService orderService;

    @Test
    void placesOrder() {
        given(paymentGateway.charge(any())).willReturn(Receipt.ok());
        orderService.place(sampleOrder());
    }
}

go deeper

for a junior

Know that mocking a bean changes the context and a frozen AOT context won't allow it.

for a middle

Explain the runtime-mutation mechanism and both the build-time and runtime failure points.

for a senior

Contrast @MockitoBean override with a @TestConfiguration bean and discuss keeping the suite AOT-eligible.

for a principal

Weigh disabling vs. refactoring against native-image coverage goals across a large test suite.

## Why mocking fights AOT ### How mock-bean overrides work (JVM mode) `@MockitoBean` (Spring Framework 6.2, the framework-level successor to Spring Boot's `@MockBean`) and `@MockitoSpyBean` tell the TestContext framework to **modify the bean factory** while the context is being created: they add a mock/spy bean definition or replace an existing one, so that `@Autowired` targets receive the Mockito object. This is a **runtime mutation of the context** performed by a Spring context customizer. ### Why AOT breaks it AOT (ahead-of-time) processing generates Java code at build time that recreates the context with a **fixed, frozen set of bean definitions** — this is what enables reflection-free startup and GraalVM native images. A frozen context is not meant to be reshaped at runtime, and the AOT-generated bean registration code doesn't re-run the mock-injecting customizers the way a live refresh does. Two failure points result: 1. **Build-time (`process-test-aot`)**: Spring refreshes each unique test context to emit optimized code. Mock-based contexts commonly can't be processed meaningfully and the AOT run errors. 2. **Runtime in AOT mode**: even if generated, the mock substitution wouldn't take effect, so the test could run against the **real** collaborator and give a misleading pass/fail. ### What @DisabledInAotMode does about it - It **excludes** the annotated class from test AOT generation, so the build step doesn't choke on it. - At runtime it **disables** the test when `spring.aot.enabled=true` (checked via `AotDetector.useGeneratedArtifacts()`), so it's reported skipped instead of running incorrectly. ## Practical guidance - Put `@DisabledInAotMode` on mock-driven `@SpringBootTest` / sliced integration tests that you also run through a native/AOT pipeline. - Pure unit tests (plain Mockito, **no** Spring context) are unaffected by AOT and need no annotation — there's no Spring context to freeze. - If you want native coverage of that behavior, refactor to a **@TestConfiguration** that supplies a test double as a normal bean the AOT context can bake in, rather than a runtime override. ## Gotchas - The annotation doesn't 'fix' the mock under AOT — it removes the test from the AOT run. - Overusing it erodes native-image test coverage; treat each use as a deliberate exception.

  • A plain unit test uses Mockito's @Mock with no Spring context. Does it need @DisabledInAotMode?
    No. AOT freezes the Spring ApplicationContext; a test with no Spring context has nothing to freeze, so it is unaffected and needs no annotation.
  • How could you keep native coverage of that behavior instead of disabling the test?
    Replace the runtime @MockitoBean override with a @TestConfiguration that defines the test double as a normal bean. AOT can bake that bean into the generated context, so the test stays AOT-compatible.

saying these in an interview costs you the question

  • Claiming @MockitoBean works fine under AOT because Spring regenerates the context per test
  • Thinking AOT contexts can register new bean definitions at runtime
  • Believing plain-Mockito unit tests (no Spring context) also need @DisabledInAotMode

context

open as a page

You've generated the AOT test artifacts. How do you actually run your tests against the AOT-optimized context on the JVM, and why would you do that instead of building a native image?

level: middleimportance: must knowfreq 45%

basics

~20 s

Set the system property spring.aot.enabled=true when running the test task. The TestContext framework then loads the build-time generated context instead of refreshing a fresh one. You do this on the JVM because it's far faster than a native build for catching AOT problems.

open as a page

Write a test that asserts a registrar registered reflection access to a type's declared constructors.

level: middleimportance: must knowfreq 45%

basics

~10 s

Run the registrar against a fresh RuntimeHints, then assert RuntimeHintsPredicates.reflection().onType(MyType.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) accepts those hints using AssertJ's .accepts(hints).

open as a page

What is @DisabledInAotMode and what problem does it solve in Spring tests?

level: juniorimportance: should knowfreq 25%

basics

~10 s

It is a Spring test annotation that skips a test when the app runs in AOT (ahead-of-time) mode. You put it on tests whose setup does not work with an AOT-optimized, pre-built application context.

open as a page

What are AOT-mode tests in Spring, and what does the process-test-aot / processTestAot step actually do?

level: juniorimportance: should knowfreq 35%

basics

~20 s

AOT (ahead-of-time) test processing pre-builds each test's Spring ApplicationContext at build time instead of at runtime. The processTestAot task (Gradle) or process-test-aot goal (Maven) generates that code so tests can run against a pre-optimized context.

open as a page

What is RuntimeHintsPredicates and why would you use it in a unit test?

level: juniorimportance: should knowfreq 35%

basics

~20 s

RuntimeHintsPredicates is a Spring test helper that builds predicates checking whether a RuntimeHintsRegistrar registered the expected reflection, resource, or proxy hints — so you can assert hints in a fast unit test instead of running a slow native build.

open as a page

What does the `nativeTest` Gradle task do in a Spring Boot native-image project?

level: juniorimportance: should knowfreq 45%

basics

~20 s

nativeTest compiles your JUnit test suite into a native executable with GraalVM and runs the tests as native code, so you verify your app behaves correctly the same way it will in a native image.

open as a page

Beyond reflection, what other hint categories can RuntimeHintsPredicates assert, and how?

level: middleimportance: should knowfreq 30%

basics

~10 s

It also covers resources, JDK proxies, and serialization: resource().forResource("...") and resource().forBundle("messages"), proxies().forInterfaces(A.class, B.class), and serialization().onType(X.class) — each returns a Predicate<RuntimeHints> you assert with AssertJ.

open as a page

What kinds of failures does running tests as a native image catch that JVM tests miss?

level: middleimportance: should knowfreq 50%

basics

~20 s

Native tests catch missing reachability metadata: unregistered reflection, JDK proxies, resources, or serialization that the JVM allows at runtime but the closed-world native image forbids. Those show up as native test failures instead of production crashes.

open as a page

Explain the dual behavior of @DisabledInAotMode (build-time vs runtime) and how Spring detects AOT mode.

level: seniorimportance: should knowfreq 35%

basics

~20 s

It acts at two moments: during build-time test AOT processing it tells Spring to skip generating optimized artifacts for that context, and at runtime it disables the test when AOT mode is active. AOT mode is detected via the spring.aot.enabled system property.

open as a page

Walk through what TestContextAotGenerator does internally when it processes your test classes. What does it emit and why is context de-duplication central?

level: seniorimportance: should knowfreq 30%

basics

~20 s

TestContextAotGenerator scans test classes, resolves each one's MergedContextConfiguration, groups tests that share an identical context, and generates one ApplicationContextInitializer per unique context plus native runtime hints and registry classes (AotTestContextInitializers, AotTestAttributes) that map tests to their context.

open as a page

Where do the hints being tested come from, and what are the limits of predicate-based hint tests in a native-readiness strategy?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Hints come from RuntimeHintsRegistrar (via @ImportRuntimeHints), @Reflective-driven processors, and AOT bean-registration contributions. Predicate tests prove a specific hint was registered but not that the app's whole hint set is complete — you still need an actual native build or AOT test.

open as a page

How does the `nativeTest` toolchain discover and run JUnit tests inside a native image, and how does Spring's test context fit in?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The GraalVM plugin uses junit-platform-native to discover JUnit Platform test descriptors and register them for reflection in the native image. Spring's processTestAot pre-generates an ApplicationContextInitializer per distinct test context so @SpringBootTest contexts start without runtime reflection.

open as a page

What kinds of tests or context features break or aren't supported under AOT-mode testing, and how do you handle them? Cover mock/bean-override behavior and @DisabledInAotMode.

level: principalimportance: should knowfreq 22%

basics

~20 s

AOT freezes the context at build time, so anything decided at runtime — dynamic bean registration, environment-dependent conditionals, context loaders that don't support AOT — can't be represented. Tests that genuinely can't run in AOT are annotated @DisabledInAotMode so they're skipped during AOT generation and AOT-mode runs.

open as a page

Where can @DisabledInAotMode be applied, and what changed across Spring versions?

level: middleimportance: nice to knowfreq 18%

basics

~10 s

It can be placed on a test class or, in newer Spring, on individual test methods. Class-level use came in Spring 6.1; Spring 6.2 added method-level use and an optional reason string.

open as a page

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%

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.

open as a page

How do you decide between testing a RuntimeHintsRegistrar in isolation with predicates versus asserting hints on an AOT-processed application context, and how do predicates fit either way?

level: principalimportance: nice to knowfreq 15%

basics

~20 s

Use isolated predicate tests for registrars you own — fast contract guards. Use AOT-processed-context hint inspection when hints emerge from bean processing, config properties, or framework integrations. RuntimeHintsPredicates is the assertion tool in both; only the RuntimeHints source differs.

open as a page

You own the CI strategy for a Spring Boot service deployed as a native image. How do you use `nativeTest` cost-effectively?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Run fast JVM tests on every commit for feedback; run nativeTest on a subset (excluding mock-heavy tests) as a scheduled/pre-release gate because each run does a slow, memory-heavy native compilation. Native tests exist to validate metadata, not to replace the JVM suite.

open as a page