Define test-specific bean configurations and components that don't interfere with production configuration detection.
org.springframework.boot.test.context
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;Full Package: org.springframework.boot.test.context.TestConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@TestComponent
public @interface TestConfiguration {
String value() default ""; // Bean name (optional)
boolean proxyBeanMethods() default true; // CGLIB proxying
}Key Differences from @Configuration:
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
@SpringBootTest
class ServiceTest {
@TestConfiguration
static class Config {
@Bean
@Primary // Overrides production bean
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Autowired
private DataSource dataSource;
@Test
void testUsesTestDataSource() {
// Uses test configuration
}
}@TestConfiguration(proxyBeanMethods = false)
static class FastConfig {
// No CGLIB proxying - faster startup
// Bean method calls create new instances
}Full Package: org.springframework.boot.test.context.TestComponent
Marks components that should only be picked up during testing.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TestComponent {
String value() default "";
}import org.springframework.boot.test.context.TestComponent;
@TestComponent
public class TestDataGenerator {
public List<TestData> generateData() {
return List.of(new TestData("test1"), new TestData("test2"));
}
}// SharedTestConfig.java
@TestConfiguration
public class SharedTestConfig {
@Bean
public Clock fixedClock() {
return Clock.fixed(Instant.parse("2024-01-01T00:00:00Z"), ZoneId.of("UTC"));
}
}
// Test classes
@SpringBootTest
@Import(SharedTestConfig.class)
class Test1 {
@Autowired Clock clock;
}
@SpringBootTest
@Import(SharedTestConfig.class)
class Test2 {
@Autowired Clock clock;
}import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@SpringBootTest
class ConditionalTestConfigTest {
@TestConfiguration
static class TestConfig {
@Bean
@ConditionalOnProperty(name = "test.feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new TestFeatureService();
}
@Bean
@ConditionalOnProperty(name = "test.feature.enabled", havingValue = "false", matchIfMissing = true)
public FeatureService disabledFeatureService() {
return new DisabledFeatureService();
}
}
@Autowired
private FeatureService featureService;
@Test
void testConditionalBean() {
assertThat(featureService).isNotNull();
}
}// DatabaseTestConfig.java
@TestConfiguration
public class DatabaseTestConfig {
@Bean
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("test-schema.sql")
.addScript("test-data.sql")
.build();
}
}
// SecurityTestConfig.java
@TestConfiguration
public class SecurityTestConfig {
@Bean
public SecurityService testSecurityService() {
return new TestSecurityService();
}
}
// Test class
@SpringBootTest
@Import({DatabaseTestConfig.class, SecurityTestConfig.class})
class MultipleTestConfigsTest {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityService securityService;
@Test
void testMultipleConfigs() {
assertThat(dataSource).isNotNull();
assertThat(securityService).isNotNull();
}
}import org.springframework.test.context.TestPropertySource;
import org.springframework.beans.factory.annotation.Value;
@SpringBootTest
class PropertySourceTestConfigTest {
@TestConfiguration
@TestPropertySource(properties = {
"test.mode=integration",
"test.timeout=5000"
})
static class TestConfig {
@Bean
public ModeService modeService(@Value("${test.mode}") String mode) {
return new ModeService(mode);
}
@Bean
public TimeoutService timeoutService(@Value("${test.timeout}") int timeout) {
return new TimeoutService(timeout);
}
}
@Autowired
private ModeService modeService;
@Autowired
private TimeoutService timeoutService;
@Test
void testPropertyInjection() {
assertThat(modeService.getMode()).isEqualTo("integration");
assertThat(timeoutService.getTimeout()).isEqualTo(5000);
}
}@SpringBootTest
class TestFixturesTest {
@TestConfiguration
static class FixtureConfig {
@Bean
public TestFixtureLoader fixtureLoader() {
TestFixtureLoader loader = new TestFixtureLoader();
loader.loadFixture("users.json");
loader.loadFixture("products.json");
loader.loadFixture("orders.json");
return loader;
}
@Bean
public TestDataCleaner dataCleaner() {
return new TestDataCleaner();
}
}
@Autowired
private TestFixtureLoader fixtureLoader;
@Autowired
private TestDataCleaner dataCleaner;
@Test
void testWithFixtures() {
assertThat(fixtureLoader.getLoadedFixtures())
.hasSize(3)
.contains("users.json", "products.json", "orders.json");
}
@AfterEach
void cleanup() {
dataCleaner.cleanTestData();
}
}import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.*;
@SpringBootTest
class MockBeanWithTestConfigTest {
@TestConfiguration
static class TestConfig {
@Bean
public ServiceA serviceA(ExternalService externalService) {
return new ServiceA(externalService);
}
@Bean
public ServiceB serviceB(ExternalService externalService) {
return new ServiceB(externalService);
}
}
@MockBean
private ExternalService externalService;
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Test
void testWithMockAndTestConfig() {
when(externalService.call()).thenReturn("mocked-response");
assertThat(serviceA.process()).contains("mocked-response");
assertThat(serviceB.process()).contains("mocked-response");
verify(externalService, times(2)).call();
}
}import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class ContextRunnerWithTestConfigTest {
@TestConfiguration
static class TestConfig {
@Bean
public MyService myService() {
return new MyService("test-mode");
}
}
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(TestConfig.class);
@Test
void testWithTestConfig() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getMode())
.isEqualTo("test-mode");
});
}
}@SpringBootTest
class BeanOverrideTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary // Overrides production bean
public EmailService emailService() {
return new MockEmailService(); // Doesn't send real emails
}
@Bean
@Primary
public PaymentGateway paymentGateway() {
return new MockPaymentGateway(); // Doesn't charge real payments
}
}
@Autowired
private EmailService emailService;
@Autowired
private PaymentGateway paymentGateway;
@Test
void testWithMockedExternalServices() {
emailService.sendEmail("test@example.com", "Test");
assertThat(((MockEmailService) emailService).getSentEmails()).hasSize(1);
paymentGateway.charge(100.00);
assertThat(((MockPaymentGateway) paymentGateway).getCharges()).hasSize(1);
}
}@SpringBootTest
@ActiveProfiles("test")
class EnvironmentSpecificTestConfigTest {
@TestConfiguration
@Profile("test")
static class TestProfileConfig {
@Bean
public DatabaseInitializer databaseInitializer() {
return new TestDatabaseInitializer();
}
}
@Autowired
private DatabaseInitializer initializer;
@Test
void testProfileSpecificBean() {
assertThat(initializer).isInstanceOf(TestDatabaseInitializer.class);
}
}@SpringBootTest
class NestedTestConfigTest {
@TestConfiguration
static class ParentConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
}
@TestConfiguration
@Import(ParentConfig.class)
static class ChildConfig {
@Bean
public ServiceB serviceB(ServiceA serviceA) {
return new ServiceB(serviceA);
}
}
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Test
void testNestedConfig() {
assertThat(serviceA).isNotNull();
assertThat(serviceB).isNotNull();
assertThat(serviceB.getServiceA()).isSameAs(serviceA);
}
}import org.springframework.boot.test.context.TestComponent;
import org.springframework.stereotype.Repository;
@TestComponent
@Repository
public class TestDataRepository {
private final Map<String, TestData> storage = new ConcurrentHashMap<>();
public TestData save(TestData data) {
storage.put(data.getId(), data);
return data;
}
public Optional<TestData> findById(String id) {
return Optional.ofNullable(storage.get(id));
}
public List<TestData> findAll() {
return new ArrayList<>(storage.values());
}
public void deleteAll() {
storage.clear();
}
}
@SpringBootTest
class TestComponentTest {
@Autowired
private TestDataRepository repository;
@Test
void testInMemoryRepository() {
TestData data = new TestData("test-1", "value");
repository.save(data);
assertThat(repository.findById("test-1"))
.isPresent()
.get()
.extracting("value")
.isEqualTo("value");
}
@AfterEach
void cleanup() {
repository.deleteAll();
}
}@TestComponent
public class TestDataGenerator {
private final Random random = new Random(42); // Fixed seed for reproducibility
public User generateUser() {
return new User(
"user-" + random.nextInt(1000),
"test" + random.nextInt(100) + "@example.com"
);
}
public List<User> generateUsers(int count) {
return IntStream.range(0, count)
.mapToObj(i -> generateUser())
.collect(Collectors.toList());
}
public Product generateProduct() {
return new Product(
"Product-" + random.nextInt(1000),
random.nextDouble() * 100
);
}
}
@SpringBootTest
class DataGeneratorTest {
@Autowired
private TestDataGenerator generator;
@Test
void testDataGeneration() {
List<User> users = generator.generateUsers(5);
assertThat(users).hasSize(5);
assertThat(users).allMatch(u -> u.getEmail().contains("@example.com"));
}
}import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@TestComponent
public class TestEventCollector {
private final List<Object> capturedEvents = new ArrayList<>();
@EventListener
public void onApplicationEvent(Object event) {
capturedEvents.add(event);
}
public List<Object> getCapturedEvents() {
return new ArrayList<>(capturedEvents);
}
public <T> List<T> getCapturedEventsOfType(Class<T> type) {
return capturedEvents.stream()
.filter(type::isInstance)
.map(type::cast)
.collect(Collectors.toList());
}
public void clear() {
capturedEvents.clear();
}
}
@SpringBootTest
class EventTest {
@Autowired
private TestEventCollector eventCollector;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Test
void testEventCapture() {
UserCreatedEvent event = new UserCreatedEvent("user-1");
eventPublisher.publishEvent(event);
assertThat(eventCollector.getCapturedEventsOfType(UserCreatedEvent.class))
.hasSize(1)
.first()
.extracting("userId")
.isEqualTo("user-1");
}
@AfterEach
void cleanup() {
eventCollector.clear();
}
}Full Package: org.springframework.boot.test.context.FilteredClassLoader
Since: 2.0.0
Test classloader that filters specific classes, packages, or resources to simulate missing dependencies or test classloading scenarios.
/**
* Test URLClassLoader that filters classes and resources
* Useful for testing auto-configuration conditional behavior
* @since 2.0.0
*/
public class FilteredClassLoader extends URLClassLoader implements SmartClassLoader {
/**
* Create FilteredClassLoader hiding specific classes
* @param hiddenClasses classes to hide from classpath
*/
public FilteredClassLoader(Class<?>... hiddenClasses);
/**
* Create FilteredClassLoader with parent hiding specific classes
* @param parent parent classloader
* @param hiddenClasses classes to hide
*/
public FilteredClassLoader(ClassLoader parent, Class<?>... hiddenClasses);
/**
* Create FilteredClassLoader hiding entire packages
* @param hiddenPackages packages to hide (e.g., "com.example")
*/
public FilteredClassLoader(String... hiddenPackages);
/**
* Create FilteredClassLoader hiding resources
* @param hiddenResources classpath resources to hide
* @since 2.1.0
*/
public FilteredClassLoader(ClassPathResource... hiddenResources);
/**
* Create FilteredClassLoader with custom predicates
* @param filters predicates returning true to hide class/resource
*/
@SafeVarargs
public FilteredClassLoader(Predicate<String>... filters);
/**
* Define a class that passes filters
* @param name class name
* @param b class bytes
* @param protectionDomain protection domain
* @return defined class
*/
public Class<?> publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain);
/**
* Filter for specific classes
*/
public static final class ClassFilter implements Predicate<String> {
public static ClassFilter of(Class<?>... hiddenClasses);
public boolean test(String className);
}
/**
* Filter for packages
*/
public static final class PackageFilter implements Predicate<String> {
public static PackageFilter of(String... hiddenPackages);
public boolean test(String className);
}
/**
* Filter for resources
* @since 2.1.0
*/
public static final class ClassPathResourceFilter implements Predicate<String> {
public static ClassPathResourceFilter of(ClassPathResource... hiddenResources);
public boolean test(String resourceName);
}
}import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class ConditionalAutoConfigTest {
@Test
void whenJacksonMissing_autoConfigDisabled() {
new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(
com.fasterxml.jackson.databind.ObjectMapper.class))
.withUserConfiguration(MyAutoConfiguration.class)
.run(context -> {
assertThat(context).doesNotHaveBean(JacksonFeature.class);
});
}
}@Test
void whenSpringSecurityMissing_securityDisabled() {
new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader("org.springframework.security"))
.withUserConfiguration(SecurityAutoConfig.class)
.run(context -> {
assertThat(context).doesNotHaveBean(SecurityFilterChain.class);
});
}import org.springframework.core.io.ClassPathResource;
@Test
void whenConfigFileMissing_usesDefaults() {
new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(
new ClassPathResource("config/custom-settings.properties")))
.withUserConfiguration(ConfigAutoConfiguration.class)
.run(context -> {
assertThat(context.getBean(Settings.class).isDefault()).isTrue();
});
}@Test
void whenMultipleDependenciesMissing_fallbackEnabled() {
new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(
org.elasticsearch.client.RestClient.class,
org.apache.kafka.clients.producer.KafkaProducer.class))
.withUserConfiguration(DataSinkAutoConfiguration.class)
.run(context -> {
assertThat(context).hasSingleBean(InMemoryDataSink.class);
assertThat(context).doesNotHaveBean(ElasticsearchSink.class);
assertThat(context).doesNotHaveBean(KafkaSink.class);
});
}@Test
void whenTestClassesFiltered_productionOnlyLoads() {
Predicate<String> testFilter = className ->
className.contains(".test.") || className.endsWith("Test");
new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(testFilter))
.withUserConfiguration(MixedConfiguration.class)
.run(context -> {
assertThat(context).doesNotHaveBean(TestHelper.class);
assertThat(context).hasSingleBean(ProductionService.class);
});
}Issue: Test configuration beans not found
Issue: Production bean not overridden
Issue: FilteredClassLoader not working with context runner
Issue: Class still loads despite filter