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.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
Hands‑On Unit Tests for Spring Boot Auto‑Configuration (Full Deep Dive)

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.imports

exists.

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=AutoConfigurationTest

Method 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+

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootAuto‑ConfigurationJUnit 5ApplicationContextRunnerConditionalOnMissingBean
CodeSmart Hoops
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.