1. 概述
在之前的 @ConfigurationProperties 使用指南 中,我们已经学习了如何在 Spring Boot 中使用 @ConfigurationProperties
注解来加载外部配置。
本文将重点讨论:如何对使用 @ConfigurationProperties
的配置类进行单元测试,确保配置项能正确绑定到对应的 Java 字段上。这是实际开发中容易踩坑的环节,尤其在复杂配置或嵌套结构下,光靠启动应用验证效率太低。
2. 依赖配置
在 Maven 项目中,我们需要引入以下两个核心依赖:
✅ **spring-boot-starter-test
**:提供 Spring 测试支持(JUnit 5、Mockito 等)
✅ **spring-boot-starter-validation
**:启用 Bean Validation(用于配置项校验)
<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-starter-validation</artifactId>
</dependency>
⚠️ 注意:spring-boot-starter-validation
虽然是可选的,但一旦你在配置类中使用了 @NotNull
、@Email
等注解,就必须引入,否则校验不会生效。
3. 将配置绑定到自定义 POJO
通常我们会为配置项创建对应的 POJO 类,Spring Boot 会自动完成属性绑定。
假设我们有如下测试配置文件 src/test/resources/server-config-test.properties
:
server.address.ip=192.168.0.1
server.resources_path.imgs=/root/imgs
对应的配置类定义如下:
@Configuration
@ConfigurationProperties(prefix = "server")
public class ServerConfig {
private Address address;
private Map<String, String> resourcesPath;
// getters and setters
}
其中 Address
是一个简单的嵌套类:
public class Address {
private String ip;
// getters and setters
}
接下来编写测试类,验证配置是否正确加载:
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = ServerConfig.class)
@TestPropertySource("classpath:server-config-test.properties")
public class BindingPropertiesToUserDefinedPOJOUnitTest {
@Autowired
private ServerConfig serverConfig;
@Test
void givenUserDefinedPOJO_whenBindingPropertiesFile_thenAllFieldsAreSet() {
assertEquals("192.168.0.1", serverConfig.getAddress().getIp());
Map<String, String> expectedResourcesPath = new HashMap<>();
expectedResourcesPath.put("imgs", "/root/imgs");
assertEquals(expectedResourcesPath, serverConfig.getResourcesPath());
}
}
关键注解说明:
- ✅
@ExtendWith(SpringExtension.class)
:让 JUnit 5 支持 Spring 测试上下文 - ✅
@EnableConfigurationProperties
:启用@ConfigurationProperties
功能,注册指定的配置 Bean - ✅
@TestPropertySource
:指定测试用的 properties 文件路径,优先级高于默认application.properties
4. 在 @Bean 方法上使用 @ConfigurationProperties
除了直接在类上标注,还可以将 @ConfigurationProperties
加在 @Bean
方法上。这种方式特别适合对接第三方库或无法修改源码的类。
例如,我们通过工厂类创建配置 Bean:
@Configuration
public class ServerConfigFactory {
@Bean(name = "default_bean")
@ConfigurationProperties(prefix = "server.default")
public ServerConfig getDefaultConfigs() {
return new ServerConfig();
}
}
对应的测试配置:
server.default.address.ip=192.168.0.2
测试类需要通过 @ContextConfiguration
显式加载配置类:
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = ServerConfig.class)
@ContextConfiguration(classes = ServerConfigFactory.class)
@TestPropertySource("classpath:server-config-test.properties")
public class BindingPropertiesToBeanMethodsUnitTest {
@Autowired
@Qualifier("default_bean")
private ServerConfig serverConfig;
@Test
void givenBeanAnnotatedMethod_whenBindingProperties_thenAllFieldsAreSet() {
assertEquals("192.168.0.2", serverConfig.getAddress().getIp());
// 其他断言...
}
}
⚠️ 注意:这里必须使用 @Qualifier("default_bean")
来注入指定名称的 Bean,否则可能因类型冲突导致注入失败。
5. 配置项校验(Validation)
Spring Boot 支持对 @ConfigurationProperties
类进行自动校验,但需要两个前提:
- 类上添加
@Validated
- 字段上使用
javax.validation
约束注解
示例配置类:
@Configuration
@ConfigurationProperties(prefix = "validate")
@Validated
public class MailServer {
@NotNull
@NotEmpty
private Map<String, @NotBlank String> propertiesMap;
@Valid
private MailConfig mailConfig = new MailConfig();
// getters and setters
}
嵌套类也需定义校验规则:
public class MailConfig {
@NotBlank
@Email
private String address;
// getters and setters
}
测试用的合法配置:
validate.propertiesMap.first=prop1
validate.propertiesMap.second=prop2
validate.mail_config.address=user1@test.com
测试代码:
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = MailServer.class)
@TestPropertySource("classpath:property-validation-test.properties")
public class PropertyValidationUnitTest {
@Autowired
private MailServer mailServer;
private static Validator propertyValidator;
@BeforeAll
public static void setup() {
propertyValidator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
void whenBindingPropertiesToValidatedBeans_thenConstrainsAreChecked() {
assertEquals(0, propertyValidator.validate(mailServer.getPropertiesMap()).size());
assertEquals(0, propertyValidator.validate(mailServer.getMailConfig()).size());
}
}
❌ 如果配置非法,例如:
validate.propertiesMap.second=
validate.mail_config.address=user1.test
Spring 启动时会直接抛出 IllegalStateException
,错误信息如下:
Property: validate.propertiesMap[second]
Value:
Reason: must not be blank
Property: validate.mailConfig.address
Value: user1.test
Reason: must be a well-formed email address
✅ 关键点:嵌套对象必须加上 @Valid
,否则即使字段为空也不会触发校验,Spring 会直接设为 null
并静默通过。
6. 配置项类型转换
Spring Boot 内置了丰富的类型转换机制,也支持自定义转换器。
6.1 Spring Boot 内置转换
支持自动转换 DataSize
、Duration
等类型。
配置示例:
# 数据大小
convert.upload_speed=500MB
convert.download_speed=10
# 时间间隔
convert.backup_day=1d
convert.backup_hour=8
对应配置类:
@Configuration
@ConfigurationProperties(prefix = "convert")
public class PropertyConversion {
private DataSize uploadSpeed;
@DataSizeUnit(DataUnit.GIGABYTES)
private DataSize downloadSpeed;
private Duration backupDay;
@DurationUnit(ChronoUnit.HOURS)
private Duration backupHour;
// getters and setters
}
测试代码验证转换结果:
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = PropertyConversion.class)
@TestPropertySource("classpath:spring-conversion-test.properties")
public class SpringPropertiesConversionUnitTest {
@Autowired
private PropertyConversion propertyConversion;
@Test
void whenUsingSpringDefaultSizeConversion_thenDataSizeObjectIsSet() {
assertEquals(DataSize.ofMegabytes(500), propertyConversion.getUploadSpeed());
assertEquals(DataSize.ofGigabytes(10), propertyConversion.getDownloadSpeed());
}
@Test
void whenUsingSpringDefaultDurationConversion_thenDurationObjectIsSet() {
assertEquals(Duration.ofDays(1), propertyConversion.getBackupDay());
assertEquals(Duration.ofHours(8), propertyConversion.getBackupHour());
}
}
6.2 自定义转换器
如果需要将字符串转换为自定义对象(如 Credentials
),可实现 Converter<String, T>
。
需求:将 user,123
转为 Credentials
对象。
convert.credentials=user,123
目标类:
public class Credentials {
private String username;
private String password;
// 构造函数、getters/setters
}
自定义转换器:
@Component
@ConfigurationPropertiesBinding
public class CustomCredentialsConverter implements Converter<String, Credentials> {
@Override
public Credentials convert(String source) {
String[] data = source.split(",");
return new Credentials(data[0], data[1]);
}
}
⚠️ 注意:
- 必须加
@Component
注册为 Spring Bean - 必须加
@ConfigurationPropertiesBinding
标识这是配置绑定专用转换器
更新 PropertyConversion
类:
public class PropertyConversion {
private Credentials credentials;
// ...
}
测试时需通过 @ContextConfiguration
加载转换器:
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = PropertyConversion.class)
@ContextConfiguration(classes = CustomCredentialsConverter.class)
@TestPropertySource("classpath:spring-conversion-test.properties")
public class SpringPropertiesConversionUnitTest {
@Autowired
private PropertyConversion propertyConversion;
@Test
void whenRegisteringCustomCredentialsConverter_thenCredentialsAreParsed() {
assertEquals("user", propertyConversion.getCredentials().getUsername());
assertEquals("123", propertyConversion.getCredentials().getPassword());
}
}
7. YAML 配置绑定
对于层级复杂或需要多环境的配置,YAML 更清晰。它还支持多 profile 定义。
测试用的 src/test/resources/application.yml
:
spring:
config:
activate:
on-profile: test
server:
address:
ip: 192.168.0.4
resources_path:
imgs: /etc/test/imgs
---
# 其他 profile
测试类写法:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
@EnableConfigurationProperties(value = ServerConfig.class)
@ActiveProfiles("test")
public class BindingYMLPropertiesUnitTest {
@Autowired
private ServerConfig serverConfig;
@Test
void whenBindingYMLConfigFile_thenAllFieldsAreSet() {
assertEquals("192.168.0.4", serverConfig.getAddress().getIp());
// 其他断言 ...
}
}
关键点:
- ✅
@ActiveProfiles("test")
:激活 test profile - ✅
@ContextConfiguration(initializers = ...)
:手动加载application.yml
- ❌
@TestPropertySource
不支持.yml
文件,只能用于.properties
8. 覆盖配置项
测试时经常需要覆盖部分配置,有两种方式:
方式一:替换整个配置文件
@TestPropertySource("classpath:test-overrides.properties")
方式二:仅覆盖特定属性(推荐)
使用 properties
属性进行内联覆盖:
@TestPropertySource(properties = {"validate.mail_config.address=new_user@test.com"})
这样就能在不修改文件的情况下动态调整配置,简单粗暴有效。
9. 总结
本文系统梳理了 @ConfigurationProperties
的测试方法,涵盖:
- ✅ 基础 POJO 绑定测试
- ✅
@Bean
方法绑定场景 - ✅ 配置校验与异常触发
- ✅ 内置与自定义类型转换
- ✅ YAML 多 profile 支持
- ✅ 测试时配置覆盖技巧
这些是保障配置正确性的关键手段,建议在项目中建立标准测试模板,避免因配置错误导致线上问题。
示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-testing