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 MapresourcesPath; // 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()); MapexpectedResourcesPath = 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 MappropertiesMap; @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 .