Spring Boot
integration testing
auto configuration
custom starter library
software development

How to integration test auto configuration for a custom Spring Boot style starter library?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When you build a custom Spring Boot starter, the important question is not just whether the configuration class compiles. The real question is whether a consumer application gets the right beans, conditions, and properties when the starter is placed on the classpath. That is why auto-configuration tests should exercise the starter the way Boot will actually wire it.

Use ApplicationContextRunner as the Default Test Tool

For starter libraries, ApplicationContextRunner is usually the best testing entry point. It boots a minimal application context, applies your auto-configuration, and lets you make assertions without starting a full app.

Suppose you have an auto-configuration class like this:

java
1@Configuration(proxyBeanMethods = false)
2@ConditionalOnClass(MyClient.class)
3@EnableConfigurationProperties(MyClientProperties.class)
4public class MyClientAutoConfiguration {
5
6    @Bean
7    @ConditionalOnMissingBean
8    MyClient myClient(MyClientProperties properties) {
9        return new MyClient(properties.getBaseUrl());
10    }
11}

A focused test can look like this:

java
1import org.junit.jupiter.api.Test;
2import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3
4import static org.assertj.core.api.Assertions.assertThat;
5
6class MyClientAutoConfigurationTest {
7
8    private final ApplicationContextRunner contextRunner =
9        new ApplicationContextRunner()
10            .withConfiguration(
11                org.springframework.boot.autoconfigure.AutoConfigurations.of(
12                    MyClientAutoConfiguration.class
13                )
14            );
15
16    @Test
17    void createsClientWhenClassIsPresent() {
18        contextRunner
19            .withPropertyValues("my.client.base-url=https://api.example.com")
20            .run(context -> {
21                assertThat(context).hasSingleBean(MyClient.class);
22                assertThat(context.getBean(MyClient.class).getBaseUrl())
23                    .isEqualTo("https://api.example.com");
24            });
25    }
26}

This style is fast, deterministic, and close to how Boot evaluates conditions.

Test Conditional Behavior Explicitly

Starter libraries often depend on conditions such as:

  • classpath presence
  • missing bean conditions
  • property flags
  • web application type

Each condition should have its own test. For example, if your starter should back off when the dependency class is missing, use FilteredClassLoader.

java
1import org.junit.jupiter.api.Test;
2import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3import org.springframework.boot.test.context.FilteredClassLoader;
4
5import static org.assertj.core.api.Assertions.assertThat;
6
7class MyClientAutoConfigurationTest {
8
9    @Test
10    void doesNotCreateClientWhenLibraryClassIsMissing() {
11        new ApplicationContextRunner()
12            .withClassLoader(new FilteredClassLoader(MyClient.class))
13            .withConfiguration(
14                org.springframework.boot.autoconfigure.AutoConfigurations.of(
15                    MyClientAutoConfiguration.class
16                )
17            )
18            .run(context -> assertThat(context).doesNotHaveBean(MyClient.class));
19    }
20}

This is the sort of test that catches real starter regressions.

Verify Bean Backoff and User Overrides

A custom starter should usually not force its bean into the context when the application already defines one. Test that explicitly.

java
1@Test
2void backsOffWhenUserProvidesBean() {
3    new ApplicationContextRunner()
4        .withUserConfiguration(UserProvidedConfig.class)
5        .withConfiguration(
6            org.springframework.boot.autoconfigure.AutoConfigurations.of(
7                MyClientAutoConfiguration.class
8            )
9        )
10        .run(context -> {
11            assertThat(context).hasSingleBean(MyClient.class);
12            assertThat(context.getBean(MyClient.class).getBaseUrl())
13                .isEqualTo("https://override.example.com");
14        });
15}

That test matters because @ConditionalOnMissingBean is one of the main promises of a well-behaved starter.

Use @SpringBootTest Sparingly

Full @SpringBootTest integration tests still have value, but they should be the minority. Use them when you need to prove that the starter works in a realistic application context with extra infrastructure, not for every small condition.

A full test might be appropriate when:

  • the starter integrates with web infrastructure
  • you need real environment binding
  • you want to check interaction across multiple auto-configurations

For most starter logic, ApplicationContextRunner is the sharper tool.

Test Registration Metadata Too

Auto-configuration is only useful if Spring Boot can discover it. For Boot 3.x, that usually means the class is listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Older Boot versions used spring.factories.

A very practical test strategy is to include at least one smoke test in a sample consumer module that depends on the built starter and boots a tiny application. That catches registration mistakes that pure unit-style context tests may miss.

Common Pitfalls

  • Using only @SpringBootTest makes starter tests slower and less precise than necessary. ApplicationContextRunner is usually the better default.
  • Testing only the happy path misses the real value of starter tests, which is verifying conditional behavior and bean backoff.
  • Forgetting to test the missing-classpath case can leave a starter failing in consumers that do not include an optional dependency.
  • Assuming the auto-configuration is discoverable without verifying registration metadata can hide packaging mistakes until runtime.
  • Asserting only that the context starts, without checking bean contents or bound properties, creates weak tests that miss configuration regressions.

Summary

  • Test starter auto-configuration the way Boot actually evaluates it, not just by calling configuration code directly.
  • Use ApplicationContextRunner for most tests because it is fast and purpose-built for auto-configuration.
  • Cover classpath conditions, property conditions, and @ConditionalOnMissingBean backoff explicitly.
  • Keep a small number of higher-level smoke tests for discovery and packaging confidence.
  • Treat starter testing as contract testing for consumer applications, not just internal unit coverage.

Course illustration
Course illustration