Test de Spring Boot @ConfigurationProperties

1. Vue d'ensemble

Dans notre guide précédent sur @ConfigurationProperties, nous avons appris comment configurer et utiliser l' annotation @ConfigurationProperties avec Spring Boot pour travailler avec une configuration externe.

Dans ce didacticiel, nous montrerons comment tester les classes de configuration qui reposent sur l' annotation @ConfigurationProperties pour nous assurer que nos données de configuration sont chargées et liées correctement à leurs champs correspondants.

2. Dépendances

Dans notre projet Maven, nous utiliserons les dépendances spring-boot-starter et spring-boot-starter-test pour activer respectivement l'API core spring et l'API de test Spring:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE   org.springframework.boot spring-boot-starter   org.springframework.boot spring-boot-starter-test test 

Aussi, configurons notre projet avec des dépendances de validation de bean puisque nous les utiliserons plus tard:

  org.hibernate hibernate-validator   javax.el javax.el-api 3.0.0   org.glassfish.web javax.el 2.2.6 

3. Propriétés liées aux POJO définis par l'utilisateur

Lorsque vous travaillez avec une configuration externalisée, nous créons généralement des POJO contenant des champs qui correspondent aux propriétés de configuration correspondantes . Comme nous le savons déjà, Spring liera automatiquement les propriétés de configuration aux classes Java que nous créons.

Pour commencer, supposons que nous ayons une configuration de serveur dans un fichier de propriétés que nous appellerons src / test / resources / server-config-test.properties :

server.address.ip=192.168.0.1 server.resources_path.imgs=/root/imgs

Maintenant, définissons une classe de configuration simple correspondant au fichier de propriétés précédent:

@Configuration @ConfigurationProperties(prefix = "server") public class ServerConfig { private Address address; private Map resourcesPath; // getters and setters }

et aussi le type d' adresse correspondant :

public class Address { private String ip; // getters and setters }

Enfin, injectons le POJO ServerConfig dans notre classe de test et validons que tous ses champs sont correctement définis:

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

Dans ce test, nous avons utilisé les annotations suivantes:

  • @ExtendWith - intègre le framework TestContext de Spring avec JUnit5
  • @EnableConfigurationProperties - active la prise en charge des beans @ConfigurationProperties (dans ce cas, le bean ServerConfig )
  • @TestPropertySource - spécifie un fichier de test qui remplace le fichier application.properties par défaut

4. @ConfigurationProperties sur les méthodes @Bean

Une autre façon de créer des haricots de configuration est en utilisant l' @ConfigurationProperties annotation sur @Bean méthodes .

Par exemple, les suivants getDefaultConfigs () méthode crée un ServerConfig grain de configuration:

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

Comme nous pouvons le constater, nous sommes en mesure de configurer l' ServerConfig instance à l' aide @ConfigurationProperties sur les getDefaultConfigs () méthode, sans avoir à modifier la ServerConfig classe elle - même. Cela peut être particulièrement utile lorsque vous travaillez avec une classe tierce externe dont l'accès est restreint.

Ensuite, définissons un exemple de propriété externe:

server.default.address.ip=192.168.0.2

Enfin, pour dire à Spring d'utiliser la classe ServerConfigFactory lors du chargement de l' ApplicationContext (par conséquent, créer notre bean de configuration), nous ajouterons l' annotation @ContextConfiguration à la classe de test:

@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. Validation des propriétés

Pour activer la validation de bean dans Spring Boot, nous devons annoter la classe de niveau supérieur avec @Validated . Ensuite, nous ajoutons les contraintes javax.validation requises :

@Configuration @ConfigurationProperties(prefix = "validate") @Validated public class MailServer { @NotNull @NotEmpty private Map propertiesMap; @Valid private MailConfig mailConfig = new MailConfig(); // getters and setters }

De même, la classe MailConfig a également quelques contraintes:

public class MailConfig { @NotBlank @Email private String address; // getters and setters }

En fournissant un ensemble de données valide:

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

l'application démarrera normalement et nos tests unitaires réussiront:

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

D'un autre côté, si nous utilisons des propriétés invalides, Spring lancera une IllegalStateException au démarrage .

Par exemple, en utilisant l'une de ces configurations non valides:

validate.propertiesMap.second= validate.mail_config.address=user1.test

entraînera l'échec de notre application, avec ce message d'erreur:

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

Notez que nous avons utilisé @Valid sur le champ mailConfig pour nous assurer que les contraintes MailConfig sont vérifiées, même si validate.mailConfig.address n'a pas été défini. Sinon, Spring définirait mailConfig sur null et démarrerait l'application normalement.

6. Conversion des propriétés

La conversion des propriétés Spring Boot nous permet de convertir certaines propriétés en types spécifiques.

Dans cette section, nous allons commencer par tester les classes de configuration qui utilisent la conversion intégrée de Spring. Ensuite, nous testerons un convertisseur personnalisé que nous créons nous-mêmes.

6.1. Conversion par défaut de Spring Boot

Considérons les propriétés de taille et de durée des données suivantes:

# data sizes convert.upload_speed=500MB convert.download_speed=10 # durations convert.backup_day=1d convert.backup_hour=8

Spring Boot liera automatiquement ces propriétés aux champs DataSize et Duration correspondants définis dans la classe de configuration 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 }

Maintenant, vérifions les résultats de la conversion:

@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. Convertisseurs personnalisés

Imaginons maintenant que nous souhaitons convertir la propriété convert.credentials :

convert.credentials=user,123

dans la classe Credential suivante :

public class Credentials { private String username; private String password; // getters and setters }

Pour y parvenir, nous pouvons implémenter un convertisseur personnalisé:

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

Enfin, ajoutons un champ Credentials à la classe PropertyConversion :

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

Dans notre classe de test SpringPropertiesConversionUnitTest , nous devons également ajouter @ContextConfiguration pour enregistrer le convertisseur personnalisé dans le contexte de Spring:

// other annotations @ContextConfiguration(classes=CustomCredentialsConverter.class) public class SpringPropertiesConversionUnitTest { //... @Test void whenRegisteringCustomCredentialsConverter_thenCredentialsAreParsed() { assertEquals("user", propertyConversion.getCredentials().getUsername()); assertEquals("123", propertyConversion.getCredentials().getPassword()); } }

Comme le montrent les assertions précédentes, Spring a utilisé notre convertisseur personnalisé pour analyser la propriété convert.credentials en une instance Credentials .

7. Reliure des documents YAML

Pour les données de configuration hiérarchique, la configuration YAML pourrait être plus pratique. De plus, YAML prend en charge la définition de plusieurs profils dans le même document.

Le fichier application.yml suivant situé sous src / test / resources / définit un profil «test» pour la classe ServerConfig :

spring: profiles: test server: address: ip: 192.168.0.4 resources_path: imgs: /etc/test/imgs --- # other profiles

En conséquence, le test suivant réussira:

@ExtendWith(SpringExtension.class) @ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.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 ... } }

Quelques notes concernant les annotations utilisées:

  • @ContextConfiguration (initializers = ConfigFileApplicationContextInitializer.cla ss) - charge le fichier application.yml
  • @ActiveProfiles («test») - spécifie que le profil «test» sera utilisé pendant ce test

Enfin, gardons à l'esprit que ni @ProperySource ni @TestProperySource ne prennent en charge le chargement de fichiers .yml . Par conséquent, nous devons toujours placer nos configurations YAML dans le fichier application.yml .

8. Remplacement des configurations @ConfigurationProperties

Parfois, nous pouvons vouloir remplacer les propriétés de configuration chargées par @ConfigurationProperties par un autre ensemble de données, en particulier lors des tests.

Comme nous l'avons montré dans les exemples précédents, nous pouvons utiliser @TestPropertySource ("path_to_new_data_set") pour remplacer toute la configuration d'origine (sous / src / main / resources) par une nouvelle.

Alternatively, we could selectively replace some of the original properties using the properties attribute of @TestPropertySource as well.

Suppose we want to override the previously defined validate.mail_config.address property with another value. All we have to do is to annotate our test class with @TestPropertySource and then assign a new value to the same property via the properties list:

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

Consequently, Spring will use the newly defined value:

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

9. Conclusion

Dans ce tutoriel, nous avons vu comment tester différents types de classes de configuration qui utilisent la @ConfigurationProperties annotation pour charger .properties et .yml fichiers de configuration.

Comme d'habitude, le code source de cet article est disponible à l'adresse over sur GitHub.