What's the difference between registering a class via withConfiguration(AutoConfigurations.of(...)) versus withUserConfiguration(...) in a runner, and why does it matter for @ConditionalOnMissingBean?
answer
- AutoConfigurations.of = auto-config: ordered + processed last
- withUserConfiguration/withBean = user beans: processed first
- @ConditionalOnMissingBean is order-sensitive
- user bean must be seen first to trigger back-off
- @AutoConfigureAfter honored only inside AutoConfigurations.of
basics
~10 sAutoConfigurations.of(...) registers classes as auto-configuration — processed last, in auto-config order, honoring @AutoConfigureAfter/Before. withUserConfiguration registers them as ordinary user beans, processed first. That ordering is exactly what @ConditionalOnMissingBean depends on.
solid answer
~40 swithConfiguration(AutoConfigurations.of(...)) wraps classes in an AutoConfigurations set so the runner treats them as real auto-configuration: they're ordered by @AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore and, crucially, evaluated after user configuration — mirroring how Spring Boot loads auto-configs at runtime. withUserConfiguration (and withBean) register classes as ordinary application config, processed before auto-configuration. This matters because @ConditionalOnMissingBean is order-sensitive: it only backs off if the competing bean is already defined when the condition is evaluated. To realistically test that a user's bean overrides your auto-config default, register the auto-config via AutoConfigurations.of(...) and the user's bean via withUserConfiguration — then the user bean is seen first and your @ConditionalOnMissingBean correctly backs off. If you wrongly register the auto-config with withUserConfiguration, ordering isn't guaranteed and @ConditionalOnMissingBean / @AutoConfigureAfter tests can give false results that won't match production.
code
java · 12 lines// auto-config under test: registered as auto-config (runs LAST)
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));
@Test
void userDefinedBeanOverridesDefault() {
runner.withUserConfiguration(UserConfig.class) // user bean: runs FIRST
.run(ctx -> assertThat(ctx).hasSingleBean(MyService.class));
}
// MyAutoConfiguration#myService is @ConditionalOnMissingBean(MyService.class);
// because it runs after UserConfig, it sees the user bean and backs off.go deeper
Likely just uses AutoConfigurations.of without knowing why.
Knows to use AutoConfigurations.of for the class under test and withUserConfiguration for stand-in user beans.
Explains that auto-config runs last and that's what makes @ConditionalOnMissingBean back off correctly.
Reasons about ordering fidelity to production, @AutoConfigureAfter/Before between auto-configs, and how mis-registration yields false-pass/false-fail tests.
## Two ways to register classes The runner offers distinct registration paths with different *semantics*: - `withConfiguration(AutoConfigurations.of(A.class, B.class))` — registers A and B as **auto-configuration** classes. `AutoConfigurations` is a special `Configurations` subtype that Spring Boot orders and processes exactly like production auto-config: sorted by `@AutoConfigureOrder`, `@AutoConfigureBefore`, `@AutoConfigureAfter`, and applied **after** all user configuration. - `withUserConfiguration(A.class)` / `withBean(...)` — registers A as ordinary **user** configuration, processed **before** auto-configuration, with no auto-config ordering semantics. ## Why ordering is not cosmetic — @ConditionalOnMissingBean `@ConditionalOnMissingBean` evaluates against the beans *already defined at the moment the condition runs*. Bean definitions are processed in a deterministic order; auto-config runs last so a user-defined bean of the same type already exists and the auto-config's `@ConditionalOnMissingBean` backs off. This is the mechanism that lets your `application.properties`-style defaults be overridden by a user's explicit `@Bean`. If you register your auto-configuration through `withUserConfiguration`, it's no longer 'last'. It competes as a peer user bean with undefined relative ordering, so `@ConditionalOnMissingBean` may evaluate *before* the user's bean is registered — producing a duplicate bean, or the wrong winner. The test would then either pass when production fails, or fail when production is fine. Both are dangerous: your test no longer models reality. ## Canonical override test ```java private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)); @Test void userBeanWinsOverAutoConfigDefault() { runner.withUserConfiguration(UserConfig.class) // user bean: processed first .run(ctx -> { assertThat(ctx).hasSingleBean(MyService.class); // exactly one assertThat(ctx).getBean(MyService.class) .isSameAs(ctx.getBean(UserConfig.class).myService()); }); } static class UserConfig { @Bean MyService myService() { return new MyService("custom"); } } ``` Here `MyAutoConfiguration#myService` is annotated `@ConditionalOnMissingBean(MyService.class)`. Because the auto-config is registered via `AutoConfigurations.of`, it runs after `UserConfig`, sees the user's bean, and backs off — leaving exactly one bean (the user's). Swap to `withUserConfiguration(MyAutoConfiguration.class)` and this guarantee evaporates. ## @AutoConfigureAfter / ordering between auto-configs The same distinction governs inter-auto-config ordering. If `BAutoConfiguration` declares `@AutoConfigureAfter(AAutoConfiguration.class)` and you want to test that B sees A's beans, both must be registered inside one `AutoConfigurations.of(A.class, B.class)` so the ordering annotations are honored. Registered as user config, the ordering hints are ignored. ## Practical rule - Class-under-test that IS an auto-configuration → `withConfiguration(AutoConfigurations.of(...))`. - Simulated user/application beans → `withUserConfiguration(...)` or `withBean(...)`. Mixing them this way makes the runner faithfully reproduce Spring Boot's real processing order, which is the whole point of using it to test conditions. ## Subtlety: withBean and order `withBean(...)` also registers as user-level and is seen before auto-config — perfect for injecting a stand-in dependency the auto-config expects, or for simulating a user-provided bean that should trigger back-off, without writing a `@Configuration` class.
- You registered your auto-config with withUserConfiguration and the @ConditionalOnMissingBean override test is flaky. Why?withUserConfiguration processes it as a peer user bean with no auto-config ordering, so the condition may run before the user's bean is registered. Register it via AutoConfigurations.of(...) so it's processed last, matching production.
- How do you make @AutoConfigureAfter ordering between two auto-configs actually take effect in a runner test?Register both in a single AutoConfigurations.of(A.class, B.class); AutoConfigurations honors @AutoConfigureAfter/Before. Registering them as user config ignores those hints.
saying these in an interview costs you the question
- Registering the auto-config-under-test with withUserConfiguration
- Believing bean processing order doesn't affect @ConditionalOnMissingBean
- Expecting @AutoConfigureAfter to work when classes are registered as user config
- Thinking AutoConfigurations.of only changes registration, not ordering/timing