ApplicationContextRunner如何簡化自動配置測試

1. 概覽

衆所周知,自動配置是Spring Boot的關鍵功能之一, 但測試自動配置可能會很棘手。git

在如下部分中,咱們將展現ApplicationContextRunner如何簡化自動配置測試github

2. 測試自動化配置方案

ApplicationContextRunner是一個實用程序類,它運行ApplicationContext並提供AssertJ樣式斷言最好用做測試類中的字段以便共享配置,而後咱們在每一個測試中進行自定義:spring

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
複製代碼

讓咱們經過測試一些案例來展現它的魔力。bash

2.1. 測試Class Condition

在本節中,咱們將測試一些使用@ConditionalOnClass和@ConditionalOnMissingClass 註解的自動配置類:spring-boot

@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.baeldung.autoconfiguration.ConditionalOnClassIntegrationTest")
protected static class ConditionalOnMissingClassConfiguration {
    @Bean
    public String missed() {
        return "This is missed when ConditionalOnClassIntegrationTest is present on the classpath";
    }
}
複製代碼

咱們想測試自動配置是否正確實例化或跳過createdmissing beans給定的預期條件。測試

  • ApplicationContextRunner爲咱們提供了withUserConfiguration方法,咱們能夠根據須要提供自動配置,以便爲每一個測試自定義ApplicationContextui

  • run 方法將 ContextConsumer 做爲將斷言應用於上下文的參數。 測試退出時,ApplicationContext將自動關閉:this

@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");
        });
}
複製代碼

經過前面的示例,咱們發現測試classpath上存在某個類的場景的簡單性。可是,當類不在classpath上時,咱們如何測試相反的狀況呢spa

這就是FilteredClassLoader發揮做用的地方。它用於在運行時過濾classpath上指定的類:code

@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. 測試 Bean Condition

咱們剛剛測試了 @ConditionalOnClass@ConditionalOnMissingClass 註解, 如今 讓咱們看看使用@ConditionalOnBean和@ConditionalOnMissingBean註釋時的狀況。

首先, 咱們一樣須要 一些自動配置的類:

@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";
    }
}
複製代碼

而後,咱們將像上一節同樣調用withUserConfiguration方法,而後發送咱們的自定義配置類來測試自動配置是否在不一樣的條件下恰當地實例化bean或跳過createOnBeancreateOnMissingBean :

@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. 測試 Property Condition

在本節中,咱們測試使用 @ConditionalOnPropertyannotations的自動配置類。

首先,咱們須要這個測試的屬性:

com.baeldung.service=custom
複製代碼

而後,咱們編寫嵌套的自動配置類,根據前面的屬性建立bean:

@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties")
protected static class SimpleServiceConfiguration {
    @Bean
    @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "default")
    @ConditionalOnMissingBean
    public DefaultService defaultService() {
        return new DefaultService();
    }
    @Bean
    @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "custom")
    @ConditionalOnMissingBean
    public CustomService customService() {
        return new CustomService();
    }
}
複製代碼

如今,咱們調用withPropertyValues方法來覆蓋每一個測試中的屬性值:

@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() {
    this.contextRunner.withPropertyValues("com.baeldung.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.baeldung.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. 結論

總結一下, 這篇教程主要展現 如何使用ApplicationContextRunner運行帶有自定義的ApplicationContext並應用斷言.

咱們在這裏介紹了最經常使用的場景,而不是列出如何自定義ApplicationContext

在此期間,請記住ApplicationConetxtRunner適用於非Web應用程序,所以請考慮WebApplicationContextRunner用於基於servlet的Web應用程序,ReactiveWebApplicationContextRunner用於響應式Web應用程序。

本文源代碼,請訪問GitHub

原文:www.baeldung.com/spring-boot…

做者:baeldung

譯者:Leesen

相關文章
相關標籤/搜索