Why are tests using @MockitoBean / @MockBean typically incompatible with AOT mode, and how does @DisabledInAotMode help?
answer
- mock override = runtime context mutation
- AOT context frozen at build time
- process-test-aot refresh fails
- @MockitoBean/@MockitoSpyBean/@MockBean
- 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 sMock-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 linesimport 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
Know that mocking a bean changes the context and a frozen AOT context won't allow it.
Explain the runtime-mutation mechanism and both the build-time and runtime failure points.
Contrast @MockitoBean override with a @TestConfiguration bean and discuss keeping the suite AOT-eligible.
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