What is Spring Boot's ApplicationContextRunner, and why would you use it instead of @SpringBootTest to test auto-configuration?
answer
- org.springframework.boot.test.context.runner
- withConfiguration + withPropertyValues + run(ctx->)
- AssertableApplicationContext + AssertJ
- isolated context, closed after run()
- fast alt to @SpringBootTest for @Conditional
basics
~10 sApplicationContextRunner is a Spring Boot test helper that boots a tiny, isolated ApplicationContext per test. You use it to check which beans your auto-configuration creates, without the cost of a full @SpringBootTest.
solid answer
~30 sApplicationContextRunner (in org.springframework.boot.test.context.runner) is a test utility for verifying auto-configuration behaviour. You configure it fluently — withConfiguration(AutoConfigurations.of(MyAutoConfig.class)), withPropertyValues(...), withBean(...) — then call run(context -> {...}). Inside the lambda you get an AssertableApplicationContext and assert with AssertJ: assertThat(context).hasSingleBean(Foo.class) or doesNotHaveBean(Foo.class). Each run() creates a fresh, fully isolated context that is closed automatically afterwards, so tests don't leak state. It's far faster than @SpringBootTest because it boots only the classes you register — no component scan, no embedded server, no full application. You reach for it precisely when you want to prove @Conditional outcomes (ConditionalOnClass, ConditionalOnMissingBean, ConditionalOnProperty) under many different property/classpath permutations cheaply.
code
java · 22 linesimport org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class MyAutoConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));
@Test
void createsServiceByDefault() {
runner.run(context ->
assertThat(context).hasSingleBean(MyService.class));
}
@Test
void backsOffWhenDisabled() {
runner.withPropertyValues("my.feature.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean(MyService.class));
}
}go deeper
Know it's a fast test helper for auto-config: register classes, run, assert beans with AssertJ.
Should fluently use withPropertyValues and hasSingleBean/doesNotHaveBean to test conditions.
Explains isolation, auto-close, and why it beats @SpringBootTest for permutation testing.
Frames it as the standard tool for library/starter authors and reasons about its narrow, fast-by-design scope.
## What it is `ApplicationContextRunner` is a class in `org.springframework.boot.test.context.runner` shipped with `spring-boot-test`. It is a *test harness* for booting a lightweight Spring `ApplicationContext` in a controlled, repeatable way so you can assert what beans end up in the context. Its primary job is testing **auto-configuration** — the `@AutoConfiguration`/`@Configuration` classes whose bean definitions are gated by `@Conditional` annotations. ## The problem it solves Auto-configuration classes are full of conditions: `@ConditionalOnClass` (only if a class is on the classpath), `@ConditionalOnMissingBean` (only if the user didn't define their own), `@ConditionalOnProperty` (only if a property is set), etc. To be confident these behave correctly you must exercise many combinations: property present vs absent, class on classpath vs removed, user bean defined vs not. Doing that with `@SpringBootTest` would be painfully slow — each variation boots a full application (component scan, embedded server, the works) and you'd need separate test classes or profiles. `ApplicationContextRunner` boots **only the classes you explicitly register**. No component scanning of your whole app, no embedded web server, no `main` application class. A single JUnit test method can run the context a dozen times with different settings in milliseconds. ## Core API shape (fluent, immutable builder) ```java new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)) .withPropertyValues("my.feature.enabled=true") .run(context -> { assertThat(context).hasSingleBean(MyService.class); }); ``` Key builder methods: - `withConfiguration(AutoConfigurations.of(...))` — register auto-configuration classes *as auto-configs* (ordering and `@AutoConfigureAfter/Before` respected). - `withUserConfiguration(...)` — register plain `@Configuration`/component classes as if the user wrote them. - `withBean(Type.class, () -> instance)` — register a bean directly. - `withPropertyValues("a=b", "c=d")` — add properties to the `Environment`. - `withSystemProperties(...)` — set (and auto-restore) JVM system properties for the run. - `withClassLoader(new FilteredClassLoader(SomeLib.class))` — simulate a class being absent from the classpath. - `withInitializer(...)` — apply an `ApplicationContextInitializer`. - `run(ContextConsumer)` — actually create the context and invoke your assertion lambda. ## The run() callback and AssertableApplicationContext `run()` takes a `ContextConsumer<AssertableApplicationContext>`. `AssertableApplicationContext` plugs into AssertJ so you can write: - `assertThat(context).hasSingleBean(Foo.class)` - `assertThat(context).doesNotHaveBean(Foo.class)` - `assertThat(context).hasBean("beanName")` - `assertThat(context).getBean(Foo.class)` (returns the bean for further assertions) - `assertThat(context).hasFailed()` / `getFailure()` (inspect a startup failure without it being thrown) The context lives **only inside the lambda** — after `run()` returns, Spring closes it automatically. That guarantees isolation between runs and prevents resource leaks. ## When to use vs not Use it for: unit-testing auto-configuration and `@Conditional` logic; library authors verifying their starter behaves correctly across classpath/property permutations. Do **not** use it as a general integration test of your whole running application — that's what `@SpringBootTest` (with real component scan, web server, and slice tests like `@WebMvcTest`) is for. `ApplicationContextRunner` is deliberately narrow and fast. ## Variants There are three: `ApplicationContextRunner` (non-web `AnnotationConfigApplicationContext`), `WebApplicationContextRunner` (servlet `AnnotationConfigServletWebApplicationContext`), and `ReactiveWebApplicationContextRunner` (reactive). Pick the one matching the context type your auto-config assumes.
- Do you have to close the context yourself after run()?No. The runner creates and closes the context around each run() call automatically, which is what keeps runs isolated. The context is only valid inside the ContextConsumer lambda.
- Can one runner instance be shared across multiple test methods safely?Yes. The runner is immutable — each with* method returns a new instance and run() creates a fresh context — so a shared field is safe and encourages reuse of common setup.
saying these in an interview costs you the question
- Thinking it boots a full application like @SpringBootTest (it only loads classes you register)
- Believing the context stays alive after run() returns
- Assuming it starts an embedded web server by default (it doesn't; that's the Web variant and still no real server)