Guide for implementing custom test slice annotations and controlling test auto-configuration.
Foundational infrastructure for domain-specific test slices enabling focused integration testing by loading only relevant components.
Key components: TestSliceTestContextBootstrapper<T>, @OverrideAutoConfiguration, Context customizers, Type exclude filters
| Component | Purpose | When to Use |
|---|---|---|
TestSliceTestContextBootstrapper<T> | Base bootstrapper for test slices | Creating custom test slice annotations |
@OverrideAutoConfiguration | Control auto-configuration loading | Disabling default auto-config for focused tests |
ContextCustomizerFactory | Customize test application context | Advanced test context modification |
TypeExcludeFilter | Filter component scanning | Control which beans are loaded in test slice |
@AutoConfigureDataSourceInitialization | Initialize DataSource with SQL scripts | Database setup for tests |
Base class for custom test slice TestContextBootstrapper instances.
package org.springframework.boot.test.autoconfigure;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.jspecify.annotations.Nullable;
import java.lang.annotation.Annotation;
/**
* Base class for test slice TestContextBootstrappers.
*
* @param <T> the test slice annotation type
*/
public abstract class TestSliceTestContextBootstrapper<T extends Annotation>
extends SpringBootTestContextBootstrapper {
/**
* Create a new TestSliceTestContextBootstrapper instance.
* The constructor resolves the generic type parameter T from the subclass
* and stores it internally for property extraction from test annotations.
*/
protected TestSliceTestContextBootstrapper() {
// Internally resolves type parameter T using ResolvableType and stores annotation type
}
/**
* Extract properties from the test slice annotation.
* Uses MergedAnnotations API to search the class hierarchy (including enclosing classes)
* and extract the "properties" attribute from annotation T.
* Override to customize property extraction.
*
* @param testClass the test class
* @return the properties array from annotation T, or null if annotation not present
*/
@Override
protected String @Nullable [] getProperties(Class<?> testClass) {
// Searches class hierarchy for annotation T and extracts properties() attribute
}
}Generic type resolution identifies test slice annotation type for automatic property extraction.
How it works: Extend with concrete type → Framework resolves T at runtime → Extracts metadata via reflection → getProperties() finds properties() attribute
package com.example.test;
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.test.context.BootstrapWith;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(MyFeatureTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(MyFeatureTypeExcludeFilter.class)
@ImportAutoConfiguration
public @interface MyFeatureTest {
String[] properties() default {};
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
Class<?>[] excludeAutoConfiguration() default {};
}package com.example.test;
import org.springframework.boot.test.autoconfigure.TestSliceTestContextBootstrapper;
public class MyFeatureTestContextBootstrapper
extends TestSliceTestContextBootstrapper<MyFeatureTest> {}package com.example.test;
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
/**
* TypeExcludeFilter for @MyFeatureTest that limits scanning to
* @MyFeatureComponent annotated beans.
*/
public class MyFeatureTypeExcludeFilter
extends StandardAnnotationCustomizableTypeExcludeFilter<MyFeatureTest> {
@Override
protected boolean hasAnnotation() {
return getAnnotation() != null;
}
@Override
protected ComponentScan.Filter[] getFilters(FilterType type) {
MyFeatureTest annotation = getAnnotation();
if (annotation == null) {
return new ComponentScan.Filter[0];
}
return switch (type) {
case INCLUDE -> annotation.includeFilters();
case EXCLUDE -> annotation.excludeFilters();
};
}
@Override
protected boolean isUseDefaultFilters() {
MyFeatureTest annotation = getAnnotation();
return annotation == null || annotation.useDefaultFilters();
}
@Override
protected Set<Class<?>> getDefaultIncludes() {
// Include @MyFeatureComponent by default
return Set.of(MyFeatureComponent.class);
}
@Override
protected Set<Class<?>> getComponentIncludes() {
// Additional component types to include
return Collections.emptySet();
}
}package com.example.test;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* Marker annotation for components that should be loaded in @MyFeatureTest.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyFeatureComponent {
}Create file: src/test/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.MyFeatureTest.imports
com.example.config.MyFeatureAutoConfiguration
com.example.config.MyFeatureDependencyAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationOne fully-qualified class name per line.
@MyFeatureTest
public class MyFeatureTests {
@Autowired
private MyFeatureService myFeatureService;
@Test
public void testFeature() {
String result = myFeatureService.process("input");
assertThat(result).isEqualTo("processed-input");
}
}Property extraction:
public class CustomBootstrapper extends TestSliceTestContextBootstrapper<MyTest> {
@Override
protected String[] getProperties(Class<?> testClass) {
MyTest annotation = testClass.getAnnotation(MyTest.class);
if (annotation == null) return null;
List<String> properties = new ArrayList<>(Arrays.asList(annotation.properties()));
if (annotation.enableCache()) properties.add("spring.cache.type=simple");
properties.add("spring.test.myfeature.enabled=true");
return properties.toArray(new String[0]);
}
}Context initialization:
public class AdvancedBootstrapper extends TestSliceTestContextBootstrapper<MyTest> {
@Override
protected Set<Class<? extends ContextCustomizer>> getContextCustomizerFactoryClasses(
MergedContextConfiguration config) {
Set<Class<? extends ContextCustomizer>> factories = new LinkedHashSet<>(
super.getContextCustomizerFactoryClasses(config));
factories.add(MyCustomContextCustomizerFactory.class);
return factories;
}
}Override @EnableAutoConfiguration behavior in tests.
package org.springframework.boot.test.autoconfigure;
/**
* Annotation that can be used to override @EnableAutoConfiguration.
* Often used with @ImportAutoConfiguration to precisely control
* which auto-configurations are loaded in tests.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OverrideAutoConfiguration {
/**
* Value of the EnableAutoConfiguration enabled override property.
* Set to false to disable auto-configuration, true to enable it.
*
* @return the override value
*/
boolean enabled();
}When @OverrideAutoConfiguration(enabled = false) is present:
spring.boot.enableautoconfiguration=false@EnableAutoConfiguration processing@ImportAutoConfigurationimport org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@OverrideAutoConfiguration(enabled = false)
public class NoAutoConfigTest {
// No auto-configurations loaded
// Manually configure all needed beans
}import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@SpringBootTest
@OverrideAutoConfiguration(enabled = false)
@ImportAutoConfiguration({
JacksonAutoConfiguration.class,
WebMvcAutoConfiguration.class
})
public class SelectiveAutoConfigTest {
// Only Jackson and WebMvc auto-configurations loaded
// Much faster startup than full @SpringBootTest
}@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@BootstrapWith(MyTestContextBootstrapper.class)
@OverrideAutoConfiguration(enabled = false) // Disable default auto-config
@ImportAutoConfiguration // Import from META-INF/spring/*.imports
public @interface MyTest {
}@MyTest // Has @OverrideAutoConfiguration(enabled = false) meta-annotation
@OverrideAutoConfiguration(enabled = true) // Override the override to enable all auto-config
public class DebugTest {
// All auto-configurations loaded for debugging
}@ImportAutoConfiguration({
DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class
})Create META-INF/spring/org.springframework.boot.test.autoconfigure.MyTest.imports:
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfigurationThen use:
@ImportAutoConfiguration // Loads from imports file@ImportAutoConfiguration(classes = {
DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class
})Enable DataSource initialization with SQL scripts in tests.
package org.springframework.boot.test.autoconfigure.jdbc;
/**
* Annotation that can be applied to test classes to enable DataSource
* initialization. Imports DataSourceInitializationAutoConfiguration which
* executes schema.sql and data.sql scripts from classpath.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
public @interface AutoConfigureDataSourceInitialization {
}Imports DataSourceInitializationAutoConfiguration which:
Default script locations:
schema.sql - Database schema creationdata.sql - Test data populationPlatform-specific scripts (higher priority):
schema-${platform}.sql - e.g., schema-h2.sql, schema-postgresql.sqldata-${platform}.sql - e.g., data-h2.sql, data-postgresql.sqlScript search order:
@SpringBootTest
@AutoConfigureDataSourceInitialization
public class DatabaseTest {
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testDatabaseInitialized() {
// schema.sql and data.sql executed before this test
Integer count = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM users", Integer.class);
assertThat(count).isGreaterThan(0); // Data from data.sql
}
}Required files:
src/test/resources/schema.sql:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255)
);src/test/resources/data.sql:
INSERT INTO users (id, username, email) VALUES (1, 'alice', 'alice@example.com');
INSERT INTO users (id, username, email) VALUES (2, 'bob', 'bob@example.com');@SpringBootTest(properties = {
"spring.sql.init.schema-locations=classpath:sql/test-schema.sql",
"spring.sql.init.data-locations=classpath:sql/test-data.sql"
})
@AutoConfigureDataSourceInitialization
public class CustomScriptLocationTest {
// Uses sql/test-schema.sql and sql/test-data.sql
}@SpringBootTest(properties = {
"spring.sql.init.schema-locations=" +
"classpath:schema/users.sql," +
"classpath:schema/products.sql," +
"classpath:schema/orders.sql",
"spring.sql.init.data-locations=" +
"classpath:data/users.sql," +
"classpath:data/products.sql"
})
@AutoConfigureDataSourceInitialization
public class MultiScriptTest {
// Executes multiple schema and data scripts in order
}@SpringBootTest(properties = {
"spring.sql.init.platform=h2"
})
@AutoConfigureDataSourceInitialization
public class PlatformSpecificTest {
// Uses schema-h2.sql and data-h2.sql instead of generic scripts
}File structure:
src/test/resources/
schema-h2.sql # H2-specific schema
schema-postgresql.sql # PostgreSQL-specific schema
data-h2.sql # H2-specific data
data-postgresql.sql # PostgreSQL-specific data@SpringBootTest(properties = {
"spring.sql.init.mode=always" // always, embedded, never
})
@AutoConfigureDataSourceInitialization
public class InitModeTest {
// Mode controls when scripts execute:
// - always: Always execute scripts
// - embedded: Only for embedded databases (default)
// - never: Never execute scripts
}@SpringBootTest(properties = {
"spring.sql.init.continue-on-error=false" // Fail on SQL errors
})
@AutoConfigureDataSourceInitialization
public class StrictInitializationTest {
// Test fails if any SQL script has errors
}Many test slices automatically include @AutoConfigureDataSourceInitialization:
@DataJpaTest // Includes @AutoConfigureDataSourceInitialization
public class JpaTest {
// schema.sql and data.sql automatically executed
}
@JdbcTest // Includes @AutoConfigureDataSourceInitialization
public class JdbcTest {
// schema.sql and data.sql automatically executed
}| Property | Type | Default | Description |
|---|---|---|---|
| spring.sql.init.mode | enum | embedded | When to execute scripts: always, embedded, never |
| spring.sql.init.schema-locations | String[] | classpath:schema.sql | Schema script locations |
| spring.sql.init.data-locations | String[] | classpath:data.sql | Data script locations |
| spring.sql.init.platform | String | all | Platform for SQL scripts |
| spring.sql.init.continue-on-error | boolean | false | Continue on SQL errors |
| spring.sql.init.separator | String | ; | SQL statement separator |
| spring.sql.init.encoding | Charset | UTF-8 | SQL script encoding |
Advanced infrastructure for customizing test application contexts.
From: org.springframework.test.context (Spring Framework)
Create custom context customizers:
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.context.ConfigurableApplicationContext;
public class MyContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
// Check if customization needed
MyTest annotation = testClass.getAnnotation(MyTest.class);
if (annotation == null) {
return null;
}
// Return customizer
return new MyContextCustomizer(annotation);
}
private static class MyContextCustomizer implements ContextCustomizer {
private final MyTest annotation;
MyContextCustomizer(MyTest annotation) {
this.annotation = annotation;
}
@Override
public void customizeContext(
ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
// Customize the context
ConfigurableEnvironment environment = context.getEnvironment();
// Add properties
Map<String, Object> properties = new HashMap<>();
properties.put("my.feature.enabled", annotation.enableFeature());
environment.getPropertySources().addFirst(
new MapPropertySource("myTestProperties", properties));
// Register beans
context.getBeanFactory().registerSingleton(
"myTestBean", new MyTestBean());
}
@Override
public boolean equals(Object obj) {
// Implement for context caching
if (this == obj) return true;
if (!(obj instanceof MyContextCustomizer other)) return false;
return Objects.equals(this.annotation, other.annotation);
}
@Override
public int hashCode() {
// Implement for context caching
return Objects.hash(annotation);
}
}
}Create META-INF/spring.factories:
org.springframework.test.context.ContextCustomizerFactory=\
com.example.test.MyContextCustomizerFactoryThis module provides internal context customizers:
OnFailureConditionReportContextCustomizerFactory (internal)
spring.test.print-condition-evaluation-reportOverrideAutoConfigurationContextCustomizerFactory (internal)
Control component scanning in test slices.
From: org.springframework.boot.test.context.filter.annotation (spring-boot-test)
Base class for test slice type filters:
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
public class MyTypeExcludeFilter
extends StandardAnnotationCustomizableTypeExcludeFilter<MyTest> {
@Override
protected boolean hasAnnotation() {
// Check if test class has @MyTest
return getAnnotation() != null;
}
@Override
protected ComponentScan.Filter[] getFilters(FilterType type) {
MyTest annotation = getAnnotation();
if (annotation == null) {
return new ComponentScan.Filter[0];
}
// Return filters from annotation
return switch (type) {
case INCLUDE -> annotation.includeFilters();
case EXCLUDE -> annotation.excludeFilters();
};
}
@Override
protected boolean isUseDefaultFilters() {
MyTest annotation = getAnnotation();
return annotation == null || annotation.useDefaultFilters();
}
@Override
protected Set<Class<?>> getDefaultIncludes() {
// Component types to include by default
return Set.of(MyFeatureComponent.class);
}
@Override
protected Set<Class<?>> getComponentIncludes() {
// Additional component types to include
return Set.of(MyConfiguration.class, MyModule.class);
}
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) {
// Custom matching logic if needed
return super.match(metadataReader, metadataReaderFactory);
}
}@TypeExcludeFilters(MyTypeExcludeFilter.class)
public @interface MyTest {
// Filter applied to component scanning
}Type: boolean Default: true
Control condition evaluation report printing on test context failures.
spring:
test:
print-condition-evaluation-report: trueWhen true and ApplicationContext fails to start, prints detailed report:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
JacksonAutoConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper'
- @ConditionalOnMissingBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) did not find any beans
Negative matches:
-----------------
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.sql.DataSource' (OnClassCondition)
Exclusions:
-----------
None
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigurationCreate test slice with property mapping:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@BootstrapWith(CachingTestContextBootstrapper.class)
@OverrideAutoConfiguration(enabled = false)
@AutoConfigureCaching // Custom annotation
@ImportAutoConfiguration
public @interface CachingTest {
String[] properties() default {};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PropertyMapping("spring.test.caching")
public @interface AutoConfigureCaching {
boolean enabled() default true;
String cacheType() default "simple";
}Use with property mapping:
@CachingTest
@AutoConfigureCaching(enabled = true, cacheType = "caffeine")
public class CacheTests {
// Properties mapped to:
// spring.test.caching.enabled=true
// spring.test.caching.cache-type=caffeine
}Create beans conditionally based on test annotation:
@Configuration
@ConditionalOnMyTest
public class MyTestAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyTestService myTestService() {
return new MyTestService();
}
@Bean
public MyTestListener myTestListener() {
return new MyTestListener();
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnMyTestCondition.class)
public @interface ConditionalOnMyTest {
}
class OnMyTestCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Check if running in @MyTest context
return context.getBeanFactory() != null
&& context.getBeanFactory().containsBean("myTestMarkerBean");
}
}Automatically configure mock beans for test slice:
@MyFeatureTest
public class MyFeatureTestWithMocks {
@Autowired
private MyFeatureService myFeatureService; // Real bean
@MockBean // From spring-boot-test
private ExternalService externalService; // Mocked dependency
@Test
public void testWithMock() {
when(externalService.fetch()).thenReturn("mocked-data");
String result = myFeatureService.process();
assertThat(result).contains("mocked-data");
}
}Use nested configuration classes in tests:
@MyFeatureTest
public class MyFeatureTests {
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public MyFeatureService testMyFeatureService() {
return new MyFeatureService(customSettings());
}
private Settings customSettings() {
return new Settings(/* test-specific settings */);
}
}
@Autowired
private MyFeatureService myFeatureService; // Uses bean from TestConfig
@Test
public void testWithCustomConfig() {
// Test with customized service
}
}Symptom: ApplicationContext initialization fails with auto-configuration errors
Solutions:
Enable condition evaluation report:
spring.test.print-condition-evaluation-report: trueReview output to see which auto-configurations matched/didn't match
Check imports file exists: META-INF/spring/org.springframework.boot.test.autoconfigure.YourTest.imports
Verify auto-configuration classes are correct:
# Imports file - must be fully qualified class names
com.example.MyAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationCheck for missing dependencies:
Exclude problematic auto-configurations:
@MyTest(excludeAutoConfiguration = ProblematicAutoConfiguration.class)Symptom: Beans expected to be loaded are not found
Solutions:
Verify TypeExcludeFilter configured correctly:
@TypeExcludeFilters(MyTypeExcludeFilter.class)
public @interface MyTest {}Check getDefaultIncludes() returns correct types:
@Override
protected Set<Class<?>> getDefaultIncludes() {
return Set.of(MyComponent.class, MyService.class);
}Verify component has expected annotation:
@MyComponent // Must have this annotation to be included
public class MyService {}Check useDefaultFilters setting:
@MyTest(useDefaultFilters = true) // Ensure not set to falseUse includeFilters to add components:
@MyTest(includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class))Symptom: Properties defined in annotation not effective in test
Solutions:
Verify properties() attribute present:
@MyTest(properties = "my.property=value")Check bootstrapper extracts properties:
@Override
protected String[] getProperties(Class<?> testClass) {
MyTest annotation = testClass.getAnnotation(MyTest.class);
return annotation != null ? annotation.properties() : null;
}Use correct property format:
@MyTest(properties = {
"key=value", // Correct
"spring.test.enabled=true" // Correct
// NOT: "key:value" or other formats
})Check for property conflicts:
Symptom: Database not initialized with schema/data scripts
Solutions:
Verify annotation present:
@SpringBootTest
@AutoConfigureDataSourceInitialization // RequiredCheck script locations:
src/test/resources/schema.sql and data.sqlCheck initialization mode:
@SpringBootTest(properties = "spring.sql.init.mode=always")
@AutoConfigureDataSourceInitializationCheck for SQL errors:
@SpringBootTest(properties = "spring.sql.init.continue-on-error=false")
@AutoConfigureDataSourceInitialization
// Will fail fast on SQL errorsVerify DataSource configured:
@Test
public void checkDataSource(@Autowired DataSource dataSource) {
assertThat(dataSource).isNotNull();
}Check platform setting:
@SpringBootTest(properties = "spring.sql.init.platform=h2")
// Looks for schema-h2.sql instead of schema.sqlSymptom: Tests slow because context recreated for each test class
Solutions:
Implement equals/hashCode in ContextCustomizer:
@Override
public boolean equals(Object obj) {
// Proper implementation enables context caching
}
@Override
public int hashCode() {
// Must match equals() implementation
}Use consistent test configuration:
Check @DirtiesContext usage:
Enable context cache logging:
logging.level.org.springframework.test.context.cache: DEBUGFocus on single concern:
@JsonTest // Good: Tests only JSON serialization
@MyServiceTest // Good: Tests only service layer
@MyFullStackTest // Bad: Too broad, defeats test slice purposeLoad minimal components:
@OverrideAutoConfiguration(enabled = false) // Disable all
@ImportAutoConfiguration({ // Import only needed
JacksonAutoConfiguration.class,
MyFeatureAutoConfiguration.class
})Use specific include filters:
@Override
protected Set<Class<?>> getDefaultIncludes() {
// Be specific about what to include
return Set.of(
MyService.class,
MyRepository.class,
MyConfiguration.class
);
}Avoid loading everything:
// Bad
@MyTest(useDefaultFilters = false) // Loads everything
// Good
@MyTest // Loads only specified component typesUse property mapping for reusable settings:
@PropertyMapping("spring.test.myfeature")
public @interface AutoConfigureMyFeature {
boolean enabled() default true;
int timeout() default 5000;
}
// Maps to spring.test.myfeature.enabled and spring.test.myfeature.timeoutProvide sensible defaults:
@MyTest(properties = {
"spring.test.myfeature.enabled=true", // Good default
"spring.test.myfeature.timeout=5000" // Reasonable timeout
})Document what test slice loads:
/**
* Test slice for MyFeature functionality.
* <p>
* Loads only:
* <ul>
* <li>@MyFeatureComponent annotated beans</li>
* <li>MyFeatureAutoConfiguration</li>
* <li>Jackson auto-configuration (for JSON)</li>
* </ul>
* <p>
* Does NOT load:
* <ul>
* <li>Web layer (controllers, filters)</li>
* <li>Database layer (repositories, entities)</li>
* <li>Security configuration</li>
* </ul>
*/
@MyFeatureTest
public class MyTests {}Use clear naming:
src/test/resources/
schema-h2.sql # H2-specific schema
schema-postgresql.sql # PostgreSQL-specific schema
data-test-users.sql # Test user data
data-test-products.sql # Test product dataKeep scripts idempotent:
-- Good: Idempotent
DROP TABLE IF EXISTS users;
CREATE TABLE users (...);
-- Bad: Fails on second run
CREATE TABLE users (...);Implement equals/hashCode for caching:
private static class MyContextCustomizer implements ContextCustomizer {
@Override
public boolean equals(Object obj) {
// Required for context caching
if (this == obj) return true;
if (!(obj instanceof MyContextCustomizer)) return false;
MyContextCustomizer other = (MyContextCustomizer) obj;
return Objects.equals(this.config, other.config);
}
@Override
public int hashCode() {
return Objects.hash(config);
}
}Annotation: java.lang.annotation.AnnotationTestContextBootstrapper: org.springframework.test.context.TestContextBootstrapperContextCustomizer: org.springframework.test.context.ContextCustomizerContextCustomizerFactory: org.springframework.test.context.ContextCustomizerFactoryContextConfigurationAttributes: org.springframework.test.context.ContextConfigurationAttributesMergedContextConfiguration: org.springframework.test.context.MergedContextConfigurationConfigurableApplicationContext: org.springframework.context.ConfigurableApplicationContextConfigurableEnvironment: org.springframework.core.env.ConfigurableEnvironmentComponentScan: org.springframework.context.annotation.ComponentScanFilter: org.springframework.context.annotation.ComponentScan.FilterFilterType: org.springframework.context.annotation.FilterTypeSpringBootTestContextBootstrapper: org.springframework.boot.test.context.SpringBootTestContextBootstrapperStandardAnnotationCustomizableTypeExcludeFilter: org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter@MockBean: org.springframework.boot.test.mock.mockito.MockBean@SpyBean: org.springframework.boot.test.mock.mockito.SpyBean@ExtendWith: org.junit.jupiter.api.extension.ExtendWithSpringExtension: org.springframework.test.context.junit.jupiter.SpringExtensionSpring Boot provides reference implementations of test slices using this infrastructure:
Location: org.springframework.boot.test.autoconfigure.json Bootstrapper: JsonTestContextBootstrapper Includes: @JacksonComponent, JacksonModule beans Auto-configurations: Jackson, Gson, JSON-B
Location: org.springframework.boot.test.autoconfigure.web.servlet (spring-boot-web-test) Bootstrapper: WebMvcTestContextBootstrapper Includes: @Controller, @ControllerAdvice, @JsonComponent Auto-configurations: Web MVC, Jackson, MessageConverters
Location: org.springframework.boot.test.autoconfigure.orm.jpa (spring-boot-data-jpa-test) Bootstrapper: DataJpaTestContextBootstrapper Includes: @Entity, @Repository, @Embeddable Auto-configurations: JPA, DataSource, Hibernate, TestEntityManager
Location: org.springframework.boot.test.autoconfigure.web.client (spring-boot-restclient-test) Bootstrapper: RestClientTestContextBootstrapper Includes: REST client components Auto-configurations: REST client, Jackson, MockRestServiceServer
Each follows the patterns documented in this guide.