Hands‑On Unit Tests for Spring Boot Auto‑Configuration (Full Deep Dive)
This article provides a step‑by‑step guide to testing Spring Boot 3.x auto‑configuration with JUnit 5 and ApplicationContextRunner, covering SpringFactoriesLoader loading, conditional annotation filtering, user‑defined bean overrides, exclusion mechanisms, and the new .imports file format, plus Maven and IDE run instructions.
Version Information
All test code targets Spring Boot 3.x + Spring Framework 6.x, requires JDK 17 or higher, and uses JUnit 5 together with Spring Boot Test.
Environment Setup
Maven Dependencies (pom.xml)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>Test Class
AutoConfigurationTest
The class contains five test methods that demonstrate core Spring Boot auto‑configuration mechanisms.
Test 1 – springFactoriesLoaderLoadsAutoConfiguration : Uses ApplicationContextRunner with AutoConfigurations.of(CacheAutoConfiguration.class) and asserts that the configuration class is loaded, the default inMemoryCacheService bean exists, and the bean implements InMemoryCacheService.
Test 2 – conditionalAnnotationFiltering : Simulates two scenarios.
Scenario 1 – no cache.type property: asserts that inMemoryCacheService is present.
Scenario 2 – cache.type=memory: asserts that inMemoryCacheService is still present.
Test 3 – conditionalOnMissingBeanUserOverride : Registers a user‑defined configuration ( UserDefinedCacheConfig) that provides a CacheService bean. Asserts the user bean exists, auto‑configured beans ( inMemoryCacheService and redisCacheService) are absent, and the retrieved CacheService returns the overridden value.
Test 4 – excludeAutoConfiguration : Demonstrates three exclusion mechanisms (annotation, property, initializer) by simulating exclusion with ApplicationContextRunner. Without exclusion, inMemoryCacheService is present; after simulated exclusion, both inMemoryCacheService and redisCacheService are absent.
Test 5 – springBoot3ImportsFileLoading : Verifies the new .imports file introduced in Spring Boot 3.x.
Checks that
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsexists.
Reads the file and asserts it contains the entry GreetingAutoConfiguration.
Loads GreetingAutoConfiguration via ApplicationContextRunner with property greeting.prefix=Test and asserts that the bean defaultGreetingService is present and its greet method returns a string containing the supplied name.
package com.example.springbootstartup.autoconfig;
import com.example.springbootstartup.condition.CacheAutoConfiguration;
import com.example.springbootstartup.condition.CacheService;
import com.example.springbootstartup.condition.InMemoryCacheService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
class AutoConfigurationTest {
@Test
void springFactoriesLoaderLoadsAutoConfiguration() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class));
runner.run(ctx -> {
assertTrue(ctx.containsBeanDefinition("cacheAutoConfiguration"));
assertTrue(ctx.containsBean("inMemoryCacheService"));
CacheService service = ctx.getBean(CacheService.class);
assertInstanceOf(InMemoryCacheService.class, service);
});
}
@Test
void conditionalAnnotationFiltering() {
// Scenario 1: no cache.type property
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class));
runner.run(ctx -> {
assertTrue(ctx.containsBean("inMemoryCacheService"), "Without cache.type the default in‑memory cache should be auto‑configured");
});
// Scenario 2: cache.type=memory
ApplicationContextRunner runner2 = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class))
.withPropertyValues("cache.type=memory");
runner2.run(ctx -> {
assertTrue(ctx.containsBean("inMemoryCacheService"), "cache.type=memory should auto‑configure the in‑memory cache");
});
}
@Test
void conditionalOnMissingBeanUserOverride() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withUserConfiguration(UserDefinedCacheConfig.class)
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class));
runner.run(ctx -> {
assertTrue(ctx.containsBean("userCacheService"));
assertFalse(ctx.containsBean("inMemoryCacheService"), "InMemoryCacheService should be skipped after user override");
assertFalse(ctx.containsBean("redisCacheService"), "RedisCacheService should be skipped after user override");
CacheService service = ctx.getBean(CacheService.class);
assertEquals("override: hello", service.get("hello", String.class), "User‑defined CacheService should be used");
});
}
@Test
void excludeAutoConfiguration() {
// Without exclusion
ApplicationContextRunner runnerWithConfig = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheAutoConfiguration.class));
runnerWithConfig.run(ctx -> {
assertTrue(ctx.containsBean("inMemoryCacheService"), "CacheService should be auto‑configured when not excluded");
});
// Simulated exclusion
ApplicationContextRunner runnerExcluded = new ApplicationContextRunner()
.withInitializer(context -> { /* manual exclusion logic could be placed here */ })
.withBean(ApplicationRunnerTestConfig.class);
runnerExcluded.run(ctx -> {
assertFalse(ctx.containsBean("inMemoryCacheService"), "CacheService should not be auto‑configured after exclusion");
assertFalse(ctx.containsBean("redisCacheService"), "CacheService should not be auto‑configured after exclusion");
});
}
@Test
void springBoot3ImportsFileLoading() {
ClassPathResource resource = new ClassPathResource("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports");
assertTrue(resource.exists(), ".imports file should exist");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
String content = reader.lines().collect(Collectors.joining("
"));
assertTrue(content.contains("GreetingAutoConfiguration"), ".imports file should contain GreetingAutoConfiguration");
} catch (Exception e) {
fail("Failed to read .imports file: " + e.getMessage());
}
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(com.example.springbootstartup.starter.GreetingAutoConfiguration.class))
.withPropertyValues("greeting.prefix=Test");
runner.run(ctx -> {
assertTrue(ctx.containsBean("defaultGreetingService"), "GreetingAutoConfiguration should be loaded");
com.example.springbootstartup.starter.GreetingService service = ctx.getBean(com.example.springbootstartup.starter.GreetingService.class);
assertNotNull(service);
assertTrue(service.greet("World").contains("World"));
});
}
@Configuration
static class UserDefinedCacheConfig {
@Bean
public CacheService userCacheService() {
return new CacheService() {
@Override
public void put(String key, Object value) {
// custom implementation
}
@Override
public <T> T get(String key, Class<T> type) {
return type.cast("override: " + key);
}
@Override
public boolean containsKey(String key) {
return false;
}
};
}
}
@Configuration
static class ApplicationRunnerTestConfig {
// intentionally left blank – no CacheService bean provided
}
}Helper Classes
CacheAutoConfiguration
package com.example.springbootstartup.condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean(CacheService.class)
@ConditionalOnProperty(prefix = "cache", name = "type", havingValue = "memory", matchIfMissing = true)
public CacheService inMemoryCacheService() {
return new InMemoryCacheService();
}
@Bean
@ConditionalOnMissingBean(CacheService.class)
@ConditionalOnProperty(prefix = "cache", name = "type", havingValue = "redis")
public CacheService redisCacheService() {
return new RedisCacheService();
}
}CacheService Interface
package com.example.springbootstartup.condition;
public interface CacheService {
void put(String key, Object value);
<T> T get(String key, Class<T> type);
boolean containsKey(String key);
}Running the Tests
Method 1: Maven Command
# Run a single test class
mvn test -Dtest=AutoConfigurationTestMethod 2: IDEA
Open any test class.
Right‑click the class name or method name.
Select “Run ‘XXX’” or “Debug ‘XXX’”.
Related Documentation
Spring Boot Auto‑Configuration Full Analysis
Applicable versions: Spring Boot 3.x + Spring Framework 6.x + JDK 17+
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
CodeSmart Hoops
A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
