Рубрики
Без рубрики

Тестирование пружинной загрузки @ConfigurationProperties

Узнайте, как протестировать классы конфигурации в Spring Boot, которые полагаются на аннотацию @ConfigurationProperties.

Автор оригинала: baeldung.

1. Обзор

В нашем предыдущем руководстве по @ConfigurationProperties , мы узнали, как настроить и использовать аннотацию @ConfigurationProperties с Spring Boot для работы с внешней конфигурацией.

В этом руководстве мы покажем, как протестировать классы конфигурации, которые полагаются на @ConfigurationProperties аннотацию , чтобы убедиться, что наши данные конфигурации загружены и правильно привязаны к соответствующим полям.

2. Зависимости

В нашем проекте Maven мы будем использовать зависимости spring-boot-starter и spring-boot-starter-test для включения основного API spring и тестового API Spring соответственно:


    org.springframework.boot
    spring-boot-starter-parent
    2.4.1

	

    org.springframework.boot
    spring-boot-starter


    org.springframework.boot
    spring-boot-starter-test
    test

Кроме того, давайте настроим наш проект с зависимостями проверки bean , так как мы будем использовать их позже:



    org.hibernate
    hibernate-validator


    javax.el
    javax.el-api
    3.0.0


    org.glassfish.web
    javax.el
    2.2.6

3. Привязка свойств к определяемым пользователем POJOS

При работе с внешней конфигурацией мы обычно создаем POJO, содержащие поля, соответствующие соответствующим свойствам конфигурации . Как мы уже знаем, Spring автоматически свяжет свойства конфигурации с классами Java, которые мы создаем.

Для начала предположим, что у нас есть некоторая конфигурация сервера внутри файла свойств, который мы назовем 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 resourcesPath;

    // getters and setters
}

а также соответствующий Адрес тип:

public class Address {

    private String ip;

    // getters and setters
}

Наконец, давайте введем Server Config 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 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. @ConfigurationProperties в методах @Bean

Другим способом создания компонентов конфигурации является использование @ConfigurationProperties аннотации на @Bean методах .

Например, следующий метод getdefaultconfig() создает компонент конфигурации Server Config configuration:

@Configuration
public class ServerConfigFactory {

    @Bean(name = "default_bean")
    @ConfigurationProperties(prefix = "server.default")
    public ServerConfig getDefaultConfigs() {
        return new ServerConfig();
    }
}

Как мы видим, мы можем настроить экземпляр Server Config с помощью @ConfigurationProperties в методе getDefaultConfigs () , не редактируя сам класс ServerConfig|/. Это может быть особенно полезно при работе с внешним сторонним классом, доступ к которому ограничен.

Далее давайте определим пример внешнего свойства:

server.default.address.ip=192.168.0.2

Наконец, чтобы указать Spring использовать класс ServerConfigFactory при загрузке ApplicationContext (следовательно, создайте наш компонент конфигурации), мы добавим аннотацию @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. Проверка свойств

Чтобы включить проверку bean в Spring Boot, мы должны аннотировать класс верхнего уровня с помощью @Validated . Затем мы добавляем необходимые javax.validation ограничения:

@Configuration
@ConfigurationProperties(prefix = "validate")
@Validated
public class MailServer {

    @NotNull
    @NotEmpty
    private Map propertiesMap;

    @Valid
    private MailConfig mailConfig = new MailConfig();

    // getters and setters
}

Аналогично, класс Mail Config также имеет некоторые ограничения:

public class MailConfig {

    @NotBlank
    @Email
    private String address;

    // getters and setters
}

Предоставляя допустимый набор данных:

validate.propertiesMap.first=prop1
validate.propertiesMap.second=prop2
[email protected]

приложение запустится нормально, и наши модульные тесты пройдут:

@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 , чтобы убедиться, что ограничения MailConfig проверены, даже если validate.mailConfig.address не был определен. В противном случае Spring установит mail Config в 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 автоматически свяжет эти свойства с соответствующими Размером данных и Длительностью полями , определенными в классе конфигурации PropertyConversion :

@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

в следующий Учетные данные класс:

public class Credentials {

    private String username;
    private String password;

    // getters and setters
}

Для достижения этой цели мы можем реализовать пользовательский конвертер:

@Component
@ConfigurationPropertiesBinding
public class CustomCredentialsConverter implements Converter {

    @Override
    public Credentials convert(String source) {
        String[] data = source.split(",");
        return new Credentials(data[0], data[1]);
    }
}

Наконец, давайте добавим поле Учетные данные в класс Преобразование свойств :

public class PropertyConversion {
    private Credentials credentials;
    // ...
}

В нашем Spring Properties Conversion Unit Test тестовом классе нам также необходимо добавить @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/ , определяет профиль “test” для класса ServerConfig :

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.cla ss) – загружает файл application.yml
  • @ActiveProfiles(“тест”) – указывает, что профиль “тест” будет использоваться во время этого теста

Наконец, давайте иметь в виду, что ни @Propertysource ни @TestProperySource поддержка загрузки .yml файлов . Поэтому мы всегда должны размещать наши конфигурации YAML в файле application.yml //.

8. Переопределение конфигураций @ConfigurationProperties

Иногда мы можем захотеть переопределить свойства конфигурации , загруженные @ConfigurationProperties с другим набором данных, особенно при тестировании.

Как мы показали в предыдущих примерах, мы можем использовать @TestPropertySource(“path_to_new_data_set”) для замены всей исходной конфигурации (в разделе /src/main/resources) на новую.

В качестве альтернативы мы могли бы выборочно заменить некоторые исходные свойства, используя атрибут properties из @TestPropertySource , а также .

Предположим, мы хотим переопределить ранее определенное свойство validate.mail_config.address другим значением. Все, что нам нужно сделать, это аннотировать наш тестовый класс с помощью @TestPropertySource , а затем присвоить новое значение тому же свойству через список properties :

@TestPropertySource(properties = {"[email protected]"})

Следовательно, Spring будет использовать вновь определенное значение:

assertEquals("[email protected]", mailServer.getMailConfig().getAddress());

9. Заключение

В этом руководстве мы рассмотрели, как тестировать различные типы классов конфигурации, которые используют аннотацию @ConfigurationProperties для загрузки файлов конфигурации .properties и .yml .

Как обычно, исходный код этой статьи доступен на GitHub .