Spring BootのApplicationContextRunnerのガイド

1. 概要

link:/spring-boot-custom-auto-configuration [自動構成はSpring Bootの重要な機能の1つ]であることはよく知られていますが、自動構成シナリオのテストは難しい場合があります。
以下のセクションでは、* __ ApplicationContextRunner ___が自動構成テストをどのように簡素化するかを示します。*

2. 自動構成シナリオのテスト

  • _ApplicationContextRunner_は、_ApplicationContext_を実行し、AssertJスタイルのアサーションを提供するユーティリティクラスです*。 共有構成の*テストクラスのフィールド*として使用するのが最適で、その後各テストでカスタマイズを行います。

private final ApplicationContextRunner contextRunner
    = new ApplicationContextRunner();
いくつかのケースをテストして、その魔法を示しましょう。

2.1. クラス条件のテスト

このセクションでは、_ @ ConditionalOnClass_および_ @ ConditionalOnMissingClass_アノテーションを使用するいくつかの自動構成クラスをテストします*:
@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";
    }
}
予想される条件で、自動構成が_created_および_missed_ Beanを適切にインスタンス化またはスキップするかどうかをテストしたいと思います。
_ApplicationContextRunner_は、テストごとに_ApplicationContext_をカスタマイズするためのオンデマンドの自動構成を提供できる_withUserConfiguration_メソッドを提供します。
_run_メソッドは、アサーションをコンテキストに適用するパラメーターとして_ContextConsumer_を取ります。 テストが終了すると、_ApplicationContext_は自動的に閉じられます。
@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");
        });
}
前の例を通して、特定のクラスがクラスパスに存在するシナリオをテストする単純さがわかります。 *しかし、クラスがクラスパスにない場合、どのように逆をテストしますか?*
これは、_FilteredClassLoader_が起動する場所です。 実行時にクラスパス上の指定されたクラスをフィルタリングするために使用されます。
@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条件

_ @ ConditionalOnClass_および_ @ ConditionalOnMissingClass_ annotationsのテストを見てきましたが、** @ 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_メソッドを呼び出し、カスタム構成クラスを送信して、自動構成が異なる条件で_createOnBean_または_createOnMissingBean_ Beanを適切にインスタンス化またはスキップするかどうかをテストします。
@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. プロパティ条件のテスト

このセクションでは、* @ ConditionalOnProperty_アノテーションを使用する自動構成クラスをテストしましょう*。
まず、このテストのプロパティが必要です。
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アプリケーション用であるため、サーブレットベースのWebアプリケーションには_WebApplicationContextRunner_を、リアクティブWebアプリケーションには_ReactiveWebApplicationContextRunner_を考慮してください。
このチュートリアルのソースコードは、https://github.com/eugenp/tutorials/tree/master/spring-boot-autoconfiguration [GitHub上]にあります。