テストサポート

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 ブローカーを埋め込むための管理ツールを提供します。

  • GreenMail は、テスト目的のオープンソースで直感的で使いやすいメールサーバーのテストスイートです。

これらのツールとライブラリのほとんどは、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 のルールと条件

LongRunningIntegrationTest JUnit 4 テストルールは、RUN_LONG_INTEGRATION_TESTS 環境またはシステムプロパティが true に設定されている場合にテストを実行する必要があるかどうかを示すために存在します。それ以外の場合はスキップされます。バージョン 5.1 以降と同じ理由で、JUnit 5 テスト用に @LongRunningTest 条件付きアノテーションが提供されています。

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

これは、受信チャネルアダプター(たとえば、AMQP 受信ゲートウェイ、JDBC ポーリングチャネルアダプター、クライアントモードの WebSocket メッセージプロデューサーなど)からターゲットシステムに実際に接続したくない場合に役立ちます。

@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 の状態を実際の構成に復元できます。

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

バージョン 6.3 以降、MockIntegrationContext.substituteTriggerFor() API が導入されました。これを使用して、AbstractPollingEndpoint 内の実際の Trigger を置き換えることができます。たとえば、本番構成は毎日 (または毎週) の cron スケジュールに依存する場合があります。カスタム Trigger をターゲットエンドポイントに挿入して、期間を軽減できます。例: 上記の OnlyOnceTrigger は、ポーリングタスクをすぐにスケジュールし、それを 1 回だけ実行する動作を提案します。

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

統合モック

org.springframework.integration.test.mock パッケージは、Spring Integration コンポーネントのアクティビティのモック、スタブ、検証のためのツールとユーティリティを提供します。モッキング機能は、よく知られている Mockito フレームワークに完全に基づいており、互換性があります。(現在の 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 サンプルなどの包括的なエンドツーエンドテストがあります。