How do you use ApplicationContextRunner to verify a @ConditionalOnProperty-guarded bean appears only when the property is set? Show the assertions.
answer
- withPropertyValues == application.properties into Environment
- hasSingleBean vs doesNotHaveBean = on vs backed-off
- test both present and absent cases
- matchIfMissing → run with no property
- use the returned runner (immutable)
basics
~10 sChain withPropertyValues("feature.enabled=true") before run(), then inside the lambda assert assertThat(context).hasSingleBean(Foo.class). For the off case, omit or set the property false and assert doesNotHaveBean(Foo.class).
solid answer
~40 sRegister the auto-config with withConfiguration(AutoConfigurations.of(...)), then drive the property via withPropertyValues, which injects entries into the context's Environment exactly as if they came from application.properties. Run twice: once with the property enabling the bean and once without. Inside each run() lambda use AssertJ against the AssertableApplicationContext — assertThat(context).hasSingleBean(MyService.class) for the enabled case and doesNotHaveBean(MyService.class) for the disabled/absent case. Because the runner is immutable, each withPropertyValues(...) returns a new configured runner, so a shared base runner field stays untouched between the two cases. This is the canonical pattern for testing @ConditionalOnProperty, and the same shape (withPropertyValues + hasSingleBean/doesNotHaveBean) covers matchIfMissing behaviour: to prove matchIfMissing=true you run with no property at all and still expect the bean.
code
java · 12 lines@Test
void respectsConditionalOnProperty() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));
// enabled -> bean present
runner.withPropertyValues("my.feature.enabled=true")
.run(ctx -> assertThat(ctx).hasSingleBean(MyService.class));
// absent -> bean backs off (no matchIfMissing)
runner.run(ctx -> assertThat(ctx).doesNotHaveBean(MyService.class));
}go deeper
Can add a property and assert the bean is or isn't there.
Tests both branches and knows the immutability pitfall of the fluent builder.
Also covers matchIfMissing and chains getBean(...) to assert configured state.
Treats condition coverage (all branches incl. matchIfMissing) as a completeness bar for a starter's test suite.
## The scenario Auto-configuration commonly gates a bean like this: ```java @AutoConfiguration public class MyAutoConfiguration { @Bean @ConditionalOnProperty(prefix = "my.feature", name = "enabled", havingValue = "true") MyService myService() { return new MyService(); } } ``` You want tests proving: (1) property `true` → bean present, (2) property `false`/absent → bean absent. ## withPropertyValues `withPropertyValues(String... pairs)` adds `key=value` entries into the context's `Environment` as a property source, identical in effect to putting them in `application.properties`. It accepts multiple pairs and can be called repeatedly (values accumulate). This is how `@ConditionalOnProperty` sees them, because that condition resolves against the `Environment`. ## The two-case test ```java private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)); @Test void beanPresentWhenEnabled() { runner.withPropertyValues("my.feature.enabled=true") .run(ctx -> assertThat(ctx).hasSingleBean(MyService.class)); } @Test void beanAbsentWhenDisabled() { runner.withPropertyValues("my.feature.enabled=false") .run(ctx -> assertThat(ctx).doesNotHaveBean(MyService.class)); } @Test void beanAbsentWhenMissing() { runner.run(ctx -> assertThat(ctx).doesNotHaveBean(MyService.class)); } ``` ## Key assertions - `hasSingleBean(Type.class)` — exactly one bean of that type. Preferred over `hasBean(name)` because it's type-based and catches accidental duplicates. - `doesNotHaveBean(Type.class)` — no bean of that type; this is what proves a condition *backed off*. - `getBean(Type.class)` — chain further AssertJ on the actual instance, e.g. `assertThat(ctx).getBean(MyService.class).extracting("timeout").isEqualTo(30)`. ## matchIfMissing gotcha If the condition uses `matchIfMissing = true`, the bean should exist even with **no** property. Test that by running with no `withPropertyValues` and asserting `hasSingleBean`. Conversely a common bug is forgetting `matchIfMissing` and being surprised the bean is absent by default — the runner makes that explicit. ## Immutability matters here Each `withPropertyValues(...)` returns a *new* runner. If you wrote `runner.withPropertyValues(...);` on its own line and then called `runner.run(...)`, the property would be lost — you must use the returned instance (chain it, as above). This is the single most common mistake. ## Property value types Everything you pass is a string, parsed by Spring's normal relaxed binding / `@ConditionalOnProperty` `havingValue` string comparison. `havingValue="true"` matches the string `true`; there's no type coercion surprise because the condition compares strings.
- You wrote `runner.withPropertyValues("x=1"); runner.run(...);` on two lines and the property isn't applied. Why?The runner is immutable — withPropertyValues returns a new instance and leaves the original unchanged. You discarded the configured instance. Chain the calls or reassign: runner = runner.withPropertyValues(...).
- How would you test that the bean is present by default via matchIfMissing=true?Run the context with no withPropertyValues at all and assert hasSingleBean. That proves the condition matches when the property is absent.
saying these in an interview costs you the question
- Calling withPropertyValues on its own line then run() on the original runner (property lost)
- Only testing the positive case and never asserting doesNotHaveBean for back-off
- Using hasBean(name) with a guessed name instead of type-based hasSingleBean
- Thinking withPropertyValues sets JVM system properties (that's withSystemProperties)