Guia do ApplicationContextRunner no Spring Boot
1. Visão geral
É bem sabido queauto-configuration is one of the key features in Spring Boot, mas testar cenários de configuração automática pode ser complicado.
Nas seções a seguir, mostraremos comoApplicationContextRunner simplifies auto-configuration testing.
2. Testar cenários de configuração automática
ApplicationContextRunner is a utility class which runs the ApplicationContext and provides AssertJ style assertions. É melhorused as a field in test class para configuração compartilhada e fazemos personalizações em cada teste depois:
private final ApplicationContextRunner contextRunner
= new ApplicationContextRunner();
Vamos mostrar sua magia testando alguns casos.
2.1. Condição da classe de teste
Nesta seção, vamostest some auto-configuration classes which use @ConditionalOnClass and @ConditionalOnMissingClass annotations:
@Configuration
@ConditionalOnClass(ConditionalOnClassIntegrationTest.class)
protected static class ConditionalOnClassConfiguration {
@Bean
public String created() {
return "This is created when ConditionalOnClassIntegrationTest "
+ "is present on the classpath";
}
}
@Configuration
@ConditionalOnMissingClass(
"com.example.autoconfiguration.ConditionalOnClassIntegrationTest"
)
protected static class ConditionalOnMissingClassConfiguration {
@Bean
public String missed() {
return "This is missed when ConditionalOnClassIntegrationTest "
+ "is present on the classpath";
}
}
Gostaríamos de testar se a configuração automática instancia ou pula os beanscreatedemissed dadas as condições esperadas.
ApplicationContextRunner nos dá o métodowithUserConfiguration onde podemos fornecer uma configuração automática sob demanda para personalizar oApplicationContext para cada teste.
O métodorun levaContextConsumer como um parâmetro que aplica as asserções ao contexto. OApplicationContext será fechado automaticamente quando o teste terminar:
@Test
public void whenDependentClassIsPresent_thenBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)
.run(context -> {
assertThat(context).hasBean("created");
assertThat(context.getBean("created"))
.isEqualTo("This is created when ConditionalOnClassIntegrationTest "
+ "is present on the classpath");
});
}
@Test
public void whenDependentClassIsPresent_thenBeanMissing() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)
.run(context -> {
assertThat(context).doesNotHaveBean("missed");
});
}
Através do exemplo anterior, vemos a simplicidade de testar os cenários em que uma determinada classe está presente no caminho de classe. But how are we going to test the converse, when the class is absent on the classpath?
É aqui queFilteredClassLoader entra em ação. É usado para filtrar classes especificadas no caminho de classe em tempo de execução:
@Test
public void whenDependentClassIsNotPresent_thenBeanMissing() {
this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)
.withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))
.run((context) -> {
assertThat(context).doesNotHaveBean("created");
assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);
});
}
@Test
public void whenDependentClassIsNotPresent_thenBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)
.withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))
.run((context) -> {
assertThat(context).hasBean("missed");
assertThat(context).getBean("missed")
.isEqualTo("This is missed when ConditionalOnClassIntegrationTest "
+ "is present on the classpath");
assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);
});
}
2.2. Condição do Bean de Teste
Acabamos de olhar as anotações de@ConditionalOnClass e@ConditionalOnMissingClass, agoralet’s see what things look like when we are using @ConditionalOnBean and @ConditionalOnMissingBean annotations.
Para começar, também precisamos dea few auto-configuration classes:
@Configuration
protected static class BasicConfiguration {
@Bean
public String created() {
return "This is always created";
}
}
@Configuration
@ConditionalOnBean(name = "created")
protected static class ConditionalOnBeanConfiguration {
@Bean
public String createOnBean() {
return "This is created when bean (name=created) is present";
}
}
@Configuration
@ConditionalOnMissingBean(name = "created")
protected static class ConditionalOnMissingBeanConfiguration {
@Bean
public String createOnMissingBean() {
return "This is created when bean (name=created) is missing";
}
}
Em seguida, chamaríamos o métodowithUserConfiguration como a seção anterior e enviaríamos nossa classe de configuração personalizada para testar se a configuração automática instanciava ou ignoravacreateOnBean oucreateOnMissingBean beans em condições diferentes :
@Test
public void whenDependentBeanIsPresent_thenConditionalBeanCreated() {
this.contextRunner.withUserConfiguration(
BasicConfiguration.class,
ConditionalOnBeanConfiguration.class
)
// ommitted for brevity
}
@Test
public void whenDependentBeanIsNotPresent_thenConditionalMissingBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingBeanConfiguration.class)
// ommitted for brevity
}
2.3. Condição da propriedade de teste
Nesta seção, vamostest the auto-configuration classes which use @ConditionalOnProperty annotations.
Primeiro, precisamos de uma propriedade para este teste:
com.example.service=custom
Depois disso, escrevemos classes de configuração automática aninhadas para criar beans com base na propriedade anterior:
@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties")
protected static class SimpleServiceConfiguration {
@Bean
@ConditionalOnProperty(name = "com.example.service", havingValue = "default")
@ConditionalOnMissingBean
public DefaultService defaultService() {
return new DefaultService();
}
@Bean
@ConditionalOnProperty(name = "com.example.service", havingValue = "custom")
@ConditionalOnMissingBean
public CustomService customService() {
return new CustomService();
}
}
Agora, estamos chamando o métodowithPropertyValues para substituir o valor da propriedade em cada teste:
@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() {
this.contextRunner.withPropertyValues("com.example.service=custom")
.withUserConfiguration(SimpleServiceConfiguration.class)
.run(context -> {
assertThat(context).hasBean("customService");
SimpleService simpleService = context.getBean(CustomService.class);
assertThat(simpleService.serve()).isEqualTo("Custom Service");
assertThat(context).doesNotHaveBean("defaultService");
});
}
@Test
public void whenGivenDefaultPropertyValue_thenDefaultServiceCreated() {
this.contextRunner.withPropertyValues("com.example.service=default")
.withUserConfiguration(SimpleServiceConfiguration.class)
.run(context -> {
assertThat(context).hasBean("defaultService");
SimpleService simpleService = context.getBean(DefaultService.class);
assertThat(simpleService.serve()).isEqualTo("Default Service");
assertThat(context).doesNotHaveBean("customService");
});
}
3. Conclusão
Para resumir, este tutorial mostrou apenashow to use ApplicationContextRunner to run the ApplicationContext with customizations and apply assertions.
Cobrimos os cenários usados com mais frequência aqui, em vez de uma lista exaustiva de como personalizar oApplicationContext.
Nesse ínterim, tenha em mente queApplicationConetxtRunner é para aplicativos não-web, então considereWebApplicationContextRunner para aplicativos web baseados em servlet eReactiveWebApplicationContextRunner para aplicativos web reativos.
O código-fonte deste tutorial pode ser encontradoover on GitHub.