知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot @ConfigurationProperties 測試

Spring Boot,Testing
HongKong
10
01:01 PM · Dec 06 ,2025

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();
    }
}

如我們所見,我們可以使用 @ConfigurationPropertiesgetDefaultConfigs() 方法中配置 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=8

Spring 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 配置文件。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.