1. 概述
在之前關於 @ConfigurationProperties 的指南中,我們學習瞭如何使用 Spring Boot 中的 @ConfigurationProperties 註解來設置和使用外部配置。
在本教程中,我們將討論如何測試依賴於 @ConfigurationProperties 註解的配置類,以確保我們的配置數據正確加載並綁定到其對應的字段。
2. 依賴項
在我們的 Maven 項目中,我們將使用 spring-boot-starter-test 依賴項以啓用 Spring 的測試 API。 此外,我們還將使用 spring-boot-starter-validation 作為 Bean 驗證依賴:
<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>3. 將用户自定義POJO綁定到屬性
當處理外部化配置時,我們通常會創建包含與匹配配置屬性相對應的字段的POJO。正如我們已經知道的,Spring 將會自動將配置屬性綁定到我們創建的 Java 類。
為了開始,讓我們假設我們有一個服務器配置位於名為 src/test/resources/server-config-test.properties 的 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
}以及對應的 類型:
public class Address {
private String ip;
// getters and setters
}最後,我們將注入 ServerConfig POJO 到我們的測試類中,並驗證其所有字段是否已正確設置:
@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 – 將 Spring TestContext 框架與 JUnit5 集成
- @EnableConfigurationProperties – 啓用對 @ConfigurationProperties Bean 的支持(在本例中是 ServerConfig Bean)
- @TestPropertySource – 指定一個測試文件,覆蓋默認的 application.properties 文件
4. <em @ConfigurationProperties</em>> 在<em @Bean> 方法中的使用`
通過在 `> 方法中使用 `> 註解,可以創建配置 Bean 的另一種方式.
例如,以下 `> 方法創建了一個 `> 配置 Bean:
@Configuration
public class ServerConfigFactory {
@Bean(name = "default_bean")
@ConfigurationProperties(prefix = "server.default")
public ServerConfig getDefaultConfigs() {
return new ServerConfig();
}
}如我們所見,我們可以使用 @ConfigurationProperties 在 getDefaultConfigs() 方法中配置 ServerConfig 實例,而無需自行編輯 ServerConfig 類本身。 這在與具有受限訪問權限的第三方類協作時尤其有用。
接下來,我們將定義一個示例外部屬性:
server.default.address.ip=192.168.0.2最後,為了讓 Spring 在加載 ApplicationContext 時使用 ServerConfigFactory 類(從而創建我們的配置 Bean),我們將向測試類添加 @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());
// other assertions...
}
}5. 屬性驗證
為了在 Spring Boot 中啓用 Bean 驗證,我們必須使用 @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
}同樣,MailConfig 類也有一些限制:
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該應用程序將正常啓動,並且我們的單元測試將通過。
@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());
}
}相反,如果使用無效屬性,Spring會在啓動時拋出 IllegalStateException。
例如,使用以下任何無效配置:
validate.propertiesMap.second=
validate.mail_config.address=user1.test將導致我們的應用程序出現以下錯誤信息:
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 屬性應用於 mailConfig 字段,以確保即使 validate.mailConfig.address 未定義時,也能對 MailConfig 約束進行校驗。 否則,Spring 會將 mailConfig 設置為 null 並正常啓動應用程序。
6. 屬性轉換
Spring Boot 屬性轉換允許我們將某些屬性轉換為特定的類型。
在本文中,我們將首先測試使用 Spring 內置轉換的配置類。然後,我們將測試我們自己創建的自定義轉換器。
6.1. Spring Boot 的默認轉換
請考慮以下數據大小和持續時間屬性:
# data sizes
convert.upload_speed=500MB
convert.download_speed=10
# durations
convert.backup_day=1d
convert.backup_hour=8Spring Boot 將自動將這些屬性綁定到定義的匹配的 和 字段上,這些字段定義在 配置類中:
@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)
@ContextConfiguration(classes = CustomCredentialsConverter.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. 自定義轉換器
現在我們假設我們要轉換 convert.credentials 屬性:
convert.credentials=user,123進入以下 Credential 類:
public class Credentials {
private String username;
private String password;
// getters and 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]);
}
}最後,我們將向 PropertyConversion 類添加一個 Credentials 字段:
public class PropertyConversion {
private Credentials credentials;
// ...
}在我們的 SpringPropertiesConversionUnitTest 測試類中,還需要添加 @ContextConfiguration 以在 Spring 的上下文中註冊自定義轉換器:
// other annotations
@ContextConfiguration(classes=CustomCredentialsConverter.class)
public class SpringPropertiesConversionUnitTest {
//...
@Test
void whenRegisteringCustomCredentialsConverter_thenCredentialsAreParsed() {
assertEquals("user", propertyConversion.getCredentials().getUsername());
assertEquals("123", propertyConversion.getCredentials().getPassword());
}
}正如之前的斷言所展示的,Spring 使用了我們的自定義轉換器來解析 convert.credentials 屬性為 Credentials 實例
7. YAML 文檔綁定
對於層次化配置數據,YAML 配置可能更方便。YAML 還支持在同一個文檔中定義多個配置文件。
以下 application.yml 位於 src/test/resources/ 中,定義了 ServerConfig 類中的“test” 配置文件:
spring:
config:
activate:
on-profile: test
server:
address:
ip: 192.168.0.4
resources_path:
imgs: /etc/test/imgs
---
# other profiles因此,以下測試將通過:
@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());
// other assertions ...
}
}關於我們使用的註解的一些説明:
- @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) – 加載 application.yml 文件
- @ActiveProfiles(“test”) – 指定在本次測試中使用 “test” 配置文件
請記住, neither @ProperySource nor @TestProperySource 支持加載 .yml 文件。 因此,我們應該始終將我們的 YAML 配置放在 application.yml 文件中。
8. 覆蓋 @ConfigurationProperties<</em/> 配置
有時,我們可能希望使用另一個數據集覆蓋由 @ConfigurationProperties<</em/> 加載的配置屬性,尤其是在測試時。
正如我們在之前的示例中看到的,我們可以使用 @TestPropertySource(“path_to_new_data_set”)<</em/> 來用一個新的配置替換原始配置(位於 /src/main/resources<</em/> 下)。
此外,我們還可以通過使用 @TestPropertySource<</em/> 的 properties<</em/> 屬性,選擇性地替換一些原始屬性。
假設我們想要覆蓋之前定義的 validate.mail_config.address<</em/> 屬性,併為其指定一個新的值。 只需要在我們的測試類上添加 @TestPropertySource<</em/> 註解,然後在 properties<</em/> 列表中為相同的屬性指定新的值即可:
@TestPropertySource(properties = {"validate.mail_config.address=new_user@test"})因此,Spring 將使用以下新定義的值:
assertEquals("new_user@test", mailServer.getMailConfig().getAddress());9. 結論
在本文中,我們學習瞭如何測試不同類型的配置類,這些類利用了 <em @ConfigurationProperties 註解來加載 .properties 和 .yml 配置文件。