テストサポート

Spring Integration は、アプリケーションのテストに役立つ多くのユーティリティとアノテーションを提供します。テストのサポートは、2 つのモジュールによって提供されます。

  • spring-integration-test-support にはコアアイテムと共有ユーティリティが含まれています

  • spring-integration-test は、統合テスト用のモックおよびアプリケーションコンテキスト構成コンポーネントを提供します

spring-integration-test-support (5.0 より前のバージョンでは spring-integration-test)は、単体テスト用の基本的なスタンドアロンユーティリティ、ルール、マッチャーを提供します。(Spring Integration 自体にも依存せず、フレームワークテストで内部的に使用されます)。spring-integration-test は、統合テストを支援することを目的としており、統合コンポーネント全体をモックし、統合フロー全体またはその一部のみを含む個々のコンポーネントの動作を検証する包括的な高レベル API を提供します。

企業でのテストの徹底的な取り扱いは、このリファレンスマニュアルの範囲を超えています。ターゲット統合ソリューションをテストするためのアイデアと原則のソースについては、Gregor Hohpe と Wendy Istvanick による “エンタープライズ統合プロジェクトでのテスト駆動開発” (英語) ペーパーを参照してください。

Spring Integration テストフレームワークおよびテストユーティリティは、既存の JUnit、Hamcrest、Mockito ライブラリに完全に基づいています。アプリケーションコンテキストの相互作用は、Spring Test フレームワークに基づいています。詳細については、それらのプロジェクトのドキュメントを参照してください。

Spring Integration、フレームワーク、その第一級オブジェクト(MessageChannelEndpointMessageHandler など)における EIP の標準実装、抽象化、疎結合の原則により、あらゆる複雑さの統合ソリューションを実装できます。フロー定義用の Spring Integration API を使用すると、統合ソリューション内の他のコンポーネント(ほとんど)に影響を与えることなく、フローの一部を改善、変更、さらには置き換えることができます。このような統合ソリューションのテストは、エンドツーエンドのアプローチと独立アプローチの両方の観点から、依然として課題となっています。いくつかの既存のツールは、一部の統合プロトコルのテストまたはモック作成に役立ち、Spring Integration チャネルアダプターでも適切に機能します。このようなツールの例は次のとおりです。

  • Spring MockMVC およびその MockRestServiceServer は、HTTP のテストに使用できます。

  • 一部の RDBMS ベンダーは、JDBC または JPA サポート用の埋め込みデータベースを提供しています。

  • ActiveMQ は、JMS または STOMP プロトコルのテスト用に埋め込むことができます。

  • 組み込み MongoDB および Redis 用のツールがあります。

  • Tomcat および Jetty には、実際の HTTP、Web サービス、WebSockets をテストするためのライブラリが組み込まれています。

  • Apache Mina プロジェクトの FtpServer と SshServer は、FTP および SFTP プロトコルのテストに使用できます。

  • Hazelcast は、テストで実際のデータグリッドノードとして実行できます。

  • キュレーターフレームワークは、Zookeeper インタラクション用の TestingServer を提供します。

  • Apache Kafka は、テストに Kafka ブローカーを埋め込むための管理ツールを提供します。

  • The GreenMail is an open-source, intuitive and easy-to-use test suite of email servers for testing purposes.

これらのツールとライブラリのほとんどは、Spring Integration テストで使用されます。また、GitHub リポジトリ [GitHub] (英語) (各モジュールの test ディレクトリ内)から、統合ソリューション用の独自のテストを作成する方法のアイデアを見つけることができます。

この章の残りの部分では、Spring Integration が提供するテストツールとユーティリティについて説明します。

テストユーティリティ

spring-integration-test-support モジュールは、ユニットテスト用のユーティリティとヘルパーを提供します。

TestUtils

次の例に示すように、TestUtils クラスは主に JUnit テストのプロパティアサーションに使用されます。

@Test
public void loadBalancerRef() {
    MessageChannel channel = channels.get("lbRefChannel");
    LoadBalancingStrategy lbStrategy = TestUtils.getPropertyValue(channel,
                 "dispatcher.loadBalancingStrategy", LoadBalancingStrategy.class);
    assertTrue(lbStrategy instanceof SampleLoadBalancingStrategy);
}

TestUtils.getPropertyValue() は Spring の DirectFieldAccessor に基づいており、ターゲットのプライベートプロパティから値を取得する機能を提供します。前の例に示すように、ドット表記を使用したネストされたプロパティアクセスもサポートしています。

createTestApplicationContext() ファクトリメソッドは、指定された Spring Integration 環境で TestApplicationContext インスタンスを生成します。

このクラスの詳細については、他の TestUtils メソッドの Javadoc を参照してください。

OnlyOnceTrigger を使用する

OnlyOnceTrigger (Javadoc) は、1 つのテストメッセージのみを生成し、他の期間のメッセージに影響を与えずに動作を検証する必要がある場合に、エンドポイントのポーリングに役立ちます。次の例は、OnlyOnceTrigger を構成する方法を示しています。

<bean id="testTrigger" class="org.springframework.integration.test.util.OnlyOnceTrigger" />

<int:poller id="jpaPoller" trigger="testTrigger">
    <int:transactional transaction-manager="transactionManager" />
</int:poller>

次の例は、前述の OnlyOnceTrigger の構成をテストに使用する方法を示しています。

@Autowired
@Qualifier("jpaPoller")
PollerMetadata poller;

@Autowired
OnlyOnceTrigger testTrigger;

@Test
@DirtiesContext
public void testWithEntityClass() throws Exception {
    this.testTrigger.reset();
    ...
    JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);

    SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter(
    		jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context,
    		this.getClass().getClassLoader());
    adapter.start();
    ...
}

サポート部品

org.springframework.integration.test.support パッケージには、ターゲットテストで実装する必要があるさまざまな抽象クラスが含まれています

JUnit 条件

@LongRunningTest 条件アノテーションは、RUN_LONG_INTEGRATION_TESTS 環境またはシステムプロパティが true に設定されている場合にテストを実行するかどうかを示します。それ以外の場合はスキップされます。

Hamcrest と Mockito マッチャー

org.springframework.integration.test.matcher パッケージには、ユニットテストで Message とそのプロパティをアサートするためのいくつかの Matcher 実装が含まれています。次の例は、そのようなマッチャー(PayloadMatcher)の使用方法を示しています。

import static org.springframework.integration.test.matcher.PayloadMatcher.hasPayload;
...
@Test
public void transform_withFilePayload_convertedToByteArray() throws Exception {
    Message<?> result = this.transformer.transform(message);
    assertThat(result, is(notNullValue()));
    assertThat(result, hasPayload(is(instanceOf(byte[].class))));
    assertThat(result, hasPayload(SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING)));
}

MockitoMessageMatchers ファクトリは、次の例に示すように、スタブと検証のモックに使用できます。

static final Date SOME_PAYLOAD = new Date();

static final String SOME_HEADER_VALUE = "bar";

static final String SOME_HEADER_KEY = "test.foo";
...
Message<?> message = MessageBuilder.withPayload(SOME_PAYLOAD)
                .setHeader(SOME_HEADER_KEY, SOME_HEADER_VALUE)
                .build();
MessageHandler handler = mock(MessageHandler.class);
handler.handleMessage(message);
verify(handler).handleMessage(messageWithPayload(SOME_PAYLOAD));
verify(handler).handleMessage(messageWithPayload(is(instanceOf(Date.class))));
...
MessageChannel channel = mock(MessageChannel.class);
when(channel.send(messageWithHeaderEntry(SOME_HEADER_KEY, is(instanceOf(Short.class)))))
        .thenReturn(true);
assertThat(channel.send(message), is(false));

AssertJ の条件と述語

バージョン 5.2 以降、MessagePredicate は AssertJ matches() アサーションで使用するために導入されました。Message オブジェクトが必要です。また、ot をヘッダーで構成して、実際のメッセージからだけでなく、期待からも除外することができます。

Spring Integration とテストコンテキスト

通常、Spring アプリケーションのテストでは、Spring Test フレームワークを使用します。Spring Integration は Spring Framework 基盤に基づいているため、Spring Test フレームワークで実行できるすべてのことは、統合フローをテストするときにも適用されます。org.springframework.integration.test.context パッケージは、統合のニーズに合わせてテストコンテキストを拡張するためのコンポーネントをいくつか提供します。最初に、次の例に示すように、Spring Integration テストフレームワークを有効にするために、@SpringIntegrationTest アノテーションを使用してテストクラスを構成します。

@SpringJUnitConfig
@SpringIntegrationTest(noAutoStartup = {"inboundChannelAdapter", "*Source*"})
public class MyIntegrationTests {

    @Autowired
    private MockIntegrationContext mockIntegrationContext;

}

@SpringIntegrationTest アノテーションは MockIntegrationContext Bean にデータを入力します。これは、テストクラスにオートワイヤーしてそのメソッドにアクセスできます。noAutoStartup オプションを使用すると、Spring Integration テストフレームワークは、通常 autoStartup=true であるエンドポイントが開始するのを防ぎます。エンドポイントは、提供されたパターンに一致します。これは、次の単純なパターンスタイルをサポートします: xxx*xxx*xxxxxx*yyy

This is useful when we would like to not have real connections to the target systems from inbound channel adapters, (for example, an AMQP Inbound Gateway, JDBC Polling Channel Adapter, WebSocket Message Producer in client mode, and so on).

@SpringIntegrationTest は org.springframework.test.context.NestedTestConfiguration セマンティクスを尊重するため、外部クラス (またはそのスーパークラス) で宣言できます。また、@SpringIntegrationTest 環境は継承された @Nested テストで使用できます。

MockIntegrationContext は、実際のアプリケーションコンテキストで Bean を変更するためのターゲットテストケースで使用することを目的としています。例: autoStartup が false にオーバーライドされているエンドポイントは、次の例に示すように、モックに置き換えることができます。

@Test
public void testMockMessageSource() {
    MessageSource<String> messageSource = () -> new GenericMessage<>("foo");

    this.mockIntegrationContext.substituteMessageSourceFor("mySourceEndpoint", messageSource);

    Message<?> receive = this.results.receive(10_000);
    assertNotNull(receive);
}
ここでの mySourceEndpoint は、実際の MessageSource をモックに置き換える SourcePollingChannelAdapter の Bean 名を指します。同様に、MockIntegrationContext.substituteMessageHandlerFor() は、エンドポイントとして MessageHandler をラップする IntegrationConsumer の Bean 名を予期します。

テストの実行後、MockIntegrationContext.resetBeans() を使用してエンドポイント Bean の状態を実際の構成に復元できます。

@AfterEach
public void tearDown() {
    this.mockIntegrationContext.resetBeans();
}

Starting with version 6.3, the MockIntegrationContext.substituteTriggerFor() API has been introduced. This can be used to replace the real Trigger in the AbstractPollingEndpoint. For example the production configuration may rely on a daily (or even weekly) cron schedule. Any custom Trigger can be injected into the target endpoint to mitigate the time span. For example, the mentioned above OnlyOnceTrigger suggests a behavior to schedule a polling task immediately and do that only once.

詳細については、Javadoc を参照してください。

統合モック

The org.springframework.integration.test.mock package offers tools and utilities for mocking, stubbing, and verification of activity on Spring Integration components. The mocking functionality is fully based on and compatible with the well-known Mockito Framework. (現在の Mockito 推移的依存関係は、バージョン 2.5.x 以降です。)

MockIntegration

MockIntegration ファクトリは、統合フロー(MessageSourceMessageProducerMessageHandlerMessageChannel)の一部である Spring Integration Bean のモックを構築するための API を提供します。次の例に示すように、構成フェーズ中およびターゲットテストメソッドでターゲットモックを使用して、検証とアサーションを実行する前に実際のエンドポイントを置き換えることができます。

<int:inbound-channel-adapter id="inboundChannelAdapter" channel="results">
    <bean class="org.springframework.integration.test.mock.MockIntegration" factory-method="mockMessageSource">
        <constructor-arg value="a"/>
        <constructor-arg>
            <array>
                <value>b</value>
                <value>c</value>
            </array>
        </constructor-arg>
    </bean>
</int:inbound-channel-adapter>

次の例は、Java 構成を使用して前述の例と同じ構成を実現する方法を示しています。

@InboundChannelAdapter(channel = "results")
@Bean
public MessageSource<Integer> testingMessageSource() {
    return MockIntegration.mockMessageSource(1, 2, 3);
}
...
StandardIntegrationFlow flow = IntegrationFlow
        .from(MockIntegration.mockMessageSource("foo", "bar", "baz"))
        .<String, String>transform(String::toUpperCase)
        .channel(out)
        .get();
IntegrationFlowRegistration registration = this.integrationFlowContext.registration(flow)
        .register();

この目的のために、次の例が示すように、前述の MockIntegrationContext をテストから使用する必要があります。

this.mockIntegrationContext.substituteMessageSourceFor("mySourceEndpoint",
        MockIntegration.mockMessageSource("foo", "bar", "baz"));
Message<?> receive = this.results.receive(10_000);
assertNotNull(receive);
assertEquals("FOO", receive.getPayload());

Mockito MessageSource モックオブジェクトとは異なり、MockMessageHandler は、受信メッセージのスタブ処理に対するチェーン API を備えた通常の AbstractMessageProducingHandler 拡張機能です。MockMessageHandler は、次のリクエストメッセージに一方向スタブを指定するための handleNext(Consumer<Message<?>>) を提供します。これは、応答を生成しないメッセージハンドラーをモックするために使用されます。handleNextAndReply(Function<Message<?>, ?>) は、次のリクエストメッセージに対して同じスタブロジックを実行し、それに対する応答を生成するために提供されています。連鎖させて、予想されるすべてのリクエストメッセージバリアントの任意のリクエスト / 応答シナリオをシミュレートできます。これらのコンシューマーと関数は、スタックから最後まで一度に 1 つずつ受信メッセージに適用され、最後のメッセージは残りのすべてのメッセージに使用されます。動作は、Mockito Answer または doReturn() API に似ています。

さらに、コンストラクター引数で MockMessageHandler に Mockito ArgumentCaptor<Message<?>> を指定できます。MockMessageHandler の各リクエストメッセージは、その ArgumentCaptor によってキャプチャーされます。テスト中に、getValue() および getAllValues() メソッドを使用して、これらのリクエストメッセージを検証およびアサートできます。

MockIntegrationContext は substituteMessageHandlerFor() API を提供します。これにより、テスト中のエンドポイントで実際に構成された MessageHandler を MockMessageHandler に置き換えることができます。

次の例は、一般的な使用シナリオを示しています。

ArgumentCaptor<Message<?>> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);

MessageHandler mockMessageHandler =
        mockMessageHandler(messageArgumentCaptor)
                .handleNextAndReply(m -> m.getPayload().toString().toUpperCase());

this.mockIntegrationContext.substituteMessageHandlerFor("myService.serviceActivator",
                               mockMessageHandler);
GenericMessage<String> message = new GenericMessage<>("foo");
this.myChannel.send(message);
Message<?> received = this.results.receive(10000);
assertNotNull(received);
assertEquals("FOO", received.getPayload());
assertSame(message, messageArgumentCaptor.getValue());
通常の MessageHandler モック (または MockMessageHandler) は、ReactiveMessageHandler 構成の ReactiveStreamsConsumer に対しても使用する必要があります。

詳細については、MockIntegration (Javadoc) および MockMessageHandler (Javadoc) Javadoc を参照してください。

その他のリソース

フレームワーク自体のテストケースを調べるだけでなく、Spring Integration サンプルリポジトリ [GitHub] (英語) には、testing-examples や advanced-testing-examples など、テストを表示するために特別に作成されたサンプルアプリケーションがいくつかあります。場合によっては、サンプル自体に file-split-ftp サンプルなどの包括的なエンドツーエンドテストがあります。