1. Обзор
В нашем предыдущем руководстве по @ConfigurationProperties мы узнали ,
как настроить и использовать аннотацию @ConfigurationProperties
с Spring Boot для работы с внешней конфигурацией.
В этом руководстве мы обсудим, как тестировать классы конфигурации, которые полагаются на аннотацию @ConfigurationProperties
, чтобы убедиться, что наши данные конфигурации загружены и правильно связаны с соответствующими полями.
2. Зависимости
В нашем проекте Maven мы будем использовать зависимости spring-boot-starter
и spring-boot-starter-test
, чтобы включить основной API Spring и тестовый API Spring. Кроме того, мы будем использовать spring-boot-starter-validation
в качестве зависимости проверки бина:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<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
:
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
}
Наконец, мы добавим POJO ServerConfig
в наш тестовый класс и проверим правильность установки всех его полей:
@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
— включает поддержкуbean-компонентов @ConfigurationProperties
(в данном случаеbean-компонента ServerConfig
)@TestPropertySource
— указывает файл тестирования, который переопределяет файлapplication.properties по умолчанию.
4. @ConfigurationProperties
в методах @Bean
Другой способ создания компонентов конфигурации — использование аннотации @ConfigurationProperties
для методов @Bean
.
Например, следующий метод getDefaultConfigs()
создает bean-компонент конфигурации ServerConfig :
@Configuration
public class ServerConfigFactory {
@Bean(name = "default_bean")
@ConfigurationProperties(prefix = "server.default")
public ServerConfig getDefaultConfigs() {
return new ServerConfig();
}
}
Как мы видим, мы можем настроить экземпляр ServerConfig
, используя @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. Проверка свойств
Чтобы включить проверку компонентов в Spring Boot, мы должны аннотировать класс верхнего уровня с помощью @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
, чтобы убедиться, что ограничения MailConfig
проверяются, даже если validate.mailConfig.address
не определен. В противном случае 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 автоматически привяжет эти свойства к соответствующим полям DataSize
и Duration
, определенным в классе конфигурации 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
в следующий класс 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]);
}
}
Наконец, мы добавим поле Credentials
в класс PropertyConversion :
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 :
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"
Наконец, имейте в виду, что ни @ProperySource
, ни @TestProperySource
не поддерживают загрузку файлов .yml
. Поэтому мы всегда должны размещать наши конфигурации YAML в файле application.yml
.
8. Переопределение конфигураций @ConfigurationProperties
Иногда нам может понадобиться переопределить свойства конфигурации, загруженные @ConfigurationProperties
, с другим набором данных, особенно при тестировании.
Как мы видели в предыдущих примерах, мы можем использовать @TestPropertySource("path_to_new_data_set")
, чтобы заменить всю исходную конфигурацию (в /src/main/resources)
новой.
В качестве альтернативы мы могли бы выборочно заменить некоторые исходные свойства, используя атрибут properties
@TestPropertySource
.
Предположим, мы хотим переопределить ранее определенное свойство validate.mail_config.address
другим значением. Все, что нам нужно сделать, это аннотировать наш тестовый класс с помощью @TestPropertySource,
а затем присвоить новое значение тому же свойству через список свойств
:
@TestPropertySource(properties = {"validate.mail_config.address=new_user@test"})
Следовательно, Spring будет использовать вновь определенное значение:
assertEquals("new_user@test", mailServer.getMailConfig().getAddress());
9. Заключение
В этой статье мы узнали, как тестировать различные типы классов конфигурации, которые используют аннотацию @ConfigurationProperties
для загрузки файлов конфигурации .properties
и .yml .
Как обычно, исходный код этой статьи доступен на GitHub .