Test di Spring Boot @ConfigurationProperties

1. Panoramica

Nella nostra precedente guida a @ConfigurationProperties, abbiamo imparato come impostare e utilizzare l' annotazione @ConfigurationProperties con Spring Boot per lavorare con la configurazione esterna.

In questo tutorial, mostreremo come testare le classi di configurazione che si basano sull'annotazione @ConfigurationProperties per assicurarci che i nostri dati di configurazione siano caricati e associati correttamente ai campi corrispondenti.

2. Dipendenze

Nel nostro progetto Maven, useremo le dipendenze spring-boot-starter e spring-boot-starter-test per abilitare rispettivamente l'API principale della molla e l'API di prova di 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 

Inoltre, configuriamo il nostro progetto con le dipendenze di convalida dei bean poiché le useremo in seguito:

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

3. Binding delle proprietà ai POJO definiti dall'utente

Quando si lavora con una configurazione esternalizzata, in genere creiamo POJO contenenti campi che corrispondono alle proprietà di configurazione corrispondenti . Come già sappiamo, Spring legherà automaticamente le proprietà di configurazione alle classi Java che creiamo.

Per cominciare, supponiamo di avere una configurazione del server all'interno di un file delle proprietà che chiameremo src / test / resources / server-config-test.properties :

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

Definiamo ora una semplice classe di configurazione corrispondente al file delle proprietà precedente:

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

e anche il tipo di indirizzo corrispondente :

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

Infine, iniettiamo il POJO ServerConfig nella nostra classe di test e convalidiamo che tutti i suoi campi siano impostati correttamente:

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

In questo test, abbiamo utilizzato le seguenti annotazioni:

  • @ExtendWith : integra il framework TestContext di Spring con JUnit5
  • @EnableConfigurationProperties - abilita il supporto per i bean @ConfigurationProperties (in questo caso, il bean ServerConfig )
  • @TestPropertySource : specifica un file di test che sovrascrive il file application.properties predefinito

4. @ConfigurationProperties su @Bean Metodi

Un altro modo per creare fagioli di configurazione è quello di utilizzare il @ConfigurationProperties annotazione @Bean metodi .

Ad esempio, il seguente metodo getDefaultConfigs () crea un bean di configurazione ServerConfig :

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

Come possiamo vedere, siamo in grado di configurare l' istanza ServerConfig utilizzando @ConfigurationProperties sul metodo getDefaultConfigs () , senza dover modificare la classe ServerConfig stessa. Ciò può essere particolarmente utile quando si lavora con una classe di terze parti esterna con accesso limitato.

Successivamente, definiamo una proprietà esterna di esempio:

server.default.address.ip=192.168.0.2

Infine, per dire a Spring di utilizzare la classe ServerConfigFactory durante il caricamento di ApplicationContext (quindi, creare il nostro bean di configurazione), aggiungeremo l' annotazione @ContextConfiguration alla classe di 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. Validazione delle proprietà

Per abilitare la convalida dei bean in Spring Boot, dobbiamo annotare la classe di primo livello con @Validated . Quindi, aggiungiamo i vincoli javax.validation richiesti :

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

Allo stesso modo, anche la classe MailConfig ha alcuni vincoli:

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

Fornendo un set di dati valido:

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

l'applicazione verrà avviata normalmente e i nostri unit test passeranno:

@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'altra parte, se usiamo proprietà non valide, Spring genererà un'eccezione IllegalStateException all'avvio .

Ad esempio, utilizzando una di queste configurazioni non valide:

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

causerà il fallimento della nostra applicazione, con questo messaggio di errore:

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

Si noti che abbiamo utilizzato @Valid nel campo mailConfig per garantire che i vincoli di MailConfig siano controllati, anche se validate.mailConfig.address non è stato definito. Altrimenti, Spring imposterà mailConfig su null e avvierà l'applicazione normalmente.

6. Conversione delle proprietà

La conversione delle proprietà di Spring Boot ci consente di convertire alcune proprietà in tipi specifici.

In questa sezione, inizieremo testando le classi di configurazione che utilizzano la conversione incorporata di Spring. Quindi, testeremo un convertitore personalizzato che creiamo noi stessi.

6.1. Conversione predefinita di Spring Boot

Consideriamo le seguenti proprietà di dimensione e durata dei dati:

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

Spring Boot legherà automaticamente queste proprietà ai campi DataSize e Duration corrispondenti definiti nella classe di configurazione 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 }

Ora controlliamo i risultati della conversione:

@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. Convertitori personalizzati

Ora immaginiamo di voler convertire la proprietà convert.credentials :

convert.credentials=user,123

nella seguente classe di credenziali :

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

Per ottenere ciò, possiamo implementare un convertitore personalizzato:

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

Infine, aggiungiamo un campo Credentials alla classe PropertyConversion :

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

Nella nostra classe di test SpringPropertiesConversionUnitTest , dobbiamo anche aggiungere @ContextConfiguration per registrare il convertitore personalizzato nel contesto di Spring:

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

Come mostrano le affermazioni precedenti, Spring ha utilizzato il nostro convertitore personalizzato per analizzare la proprietà convert.credentials in un'istanza di Credentials .

7. Rilegatura di documenti YAML

Per i dati di configurazione gerarchica, la configurazione YAML potrebbe essere più conveniente. Inoltre, YAML supporta la definizione di più profili all'interno dello stesso documento.

Il seguente application.yml situato in src / test / resources / definisce un profilo "test" per la classe ServerConfig :

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

Di conseguenza, il seguente test passerà:

@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 ... } }

Un paio di note riguardanti le annotazioni utilizzate:

  • @ContextConfiguration (initializers = ConfigFileApplicationContextInitializer.cla ss) - carica il file application.yml
  • @ActiveProfiles ("test") - specifica che il profilo "test" verrà utilizzato durante questo test

Infine, tieni presente che @ProperySource@TestProperySource supportano il caricamento di file .yml . Pertanto, dovremmo sempre posizionare le nostre configurazioni YAML all'interno del file application.yml .

8. Sovrascrittura delle configurazioni @ConfigurationProperties

A volte, potremmo voler sovrascrivere le proprietà di configurazione caricate da @ConfigurationProperties con un altro set di dati, in particolare durante il test.

Come abbiamo mostrato negli esempi precedenti, possiamo usare @TestPropertySource ("path_to_new_data_set") per sostituire l'intera configurazione originale (sotto / src / main / resources) con una nuova.

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

In questo tutorial, abbiamo visto come testare diversi tipi di classi di configurazione che fanno uso dell'annotazione @ConfigurationProperties per caricare i file di configurazione .properties e .yml .

Come al solito, il codice sorgente di questo articolo è disponibile su GitHub.