Guia do ApplicationContextRunner no Spring Boot

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.