最新の安定バージョンについては、spring-cloud-stream 5.0.1 を使用してください。

テスト

Spring Cloud Stream は、メッセージングシステムに接続せずにマイクロサービスアプリケーションをテストするためのサポートを提供します。

Spring Integration テストバインダー

Spring Cloud Stream には、実際のバインダー実装やメッセージブローカーを必要とせずに、さまざまなアプリケーションコンポーネントをテストするために使用できるテストバインダーが付属しています。

このテストバインダーは、単体テスト統合テスト間のブリッジとして機能し、JVM 内メッセージブローカーとして Spring Integration フレームワークに基づいており、本質的に両方の長所を提供します。つまり、ネットワークのない実際のバインダーです。

バインダー構成のテスト

Spring Integration テストバインダーを有効にするには、それを依存関係として追加し、クラスに @EnableTestBinder のアノテーションを付ける必要があります。

必須依存関係を追加

以下は、必要な Maven POM エントリの例です。

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-stream-test-binder</artifactId>
	<scope>test</scope>
</dependency>

または build.gradle.kts の場合

testImplementation("org.springframework.cloud:spring-cloud-stream-test-binder")

バインダーの使用箇所をテストする

これで、マイクロサービスを単純な単体テストとしてテストできます。テストバインダーを有効にするには、クラスに @EnableTestBinder アノテーションを付けます。

@SpringBootTest
public class SampleStreamTests {

	@Autowired
	private InputDestination input;

	@Autowired
	private OutputDestination output;

	@Test
	public void testEmptyConfiguration() {
		this.input.send(new GenericMessage<byte[]>("hello".getBytes()));
		assertThat(output.receive().getPayload()).isEqualTo("HELLO".getBytes());
	}

	@SpringBootApplication
	@EnableTestBinder
	public static class SampleConfiguration {
		@Bean
		public Function<String, String> uppercase() {
			return v -> v.toUpperCase();
		}
	}
}

また、より詳細な制御が必要な場合、同じテストスイートで複数の構成をテストする場合は、次の操作も実行できます。

@EnableAutoConfiguration
public static class MyTestConfiguration {
	@Bean
	public Function<String, String> uppercase() {
			return v -> v.toUpperCase();
	}
}

. . .

@Test
public void sampleTest() {
	try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
				TestChannelBinderConfiguration.getCompleteConfiguration(
						MyTestConfiguration.class))
				.run("--spring.cloud.function.definition=uppercase")) {
		InputDestination source = context.getBean(InputDestination.class);
		OutputDestination target = context.getBean(OutputDestination.class);
		source.send(new GenericMessage<byte[]>("hello".getBytes()));
		assertThat(target.receive().getPayload()).isEqualTo("HELLO".getBytes());
	}
}

複数のバインディングや複数の入力と出力がある場合、または単に送受信する宛先の名前を明示したい場合は、InputDestination と OutputDestination の send() メソッドと receive() メソッドをオーバーライドして、提供できるようにします。入力および出力先の名前。

次のサンプルについて考えてみます。

@EnableAutoConfiguration
public static class SampleFunctionConfiguration {

	@Bean
	public Function<String, String> uppercase() {
		return value -> value.toUpperCase();
	}

	@Bean
	public Function<String, String> reverse() {
		return value -> new StringBuilder(value).reverse().toString();
	}
}

そして実際のテスト

@Test
public void testMultipleFunctions() {
	try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
			TestChannelBinderConfiguration.getCompleteConfiguration(
					SampleFunctionConfiguration.class))
							.run("--spring.cloud.function.definition=uppercase;reverse")) {

		InputDestination inputDestination = context.getBean(InputDestination.class);
		OutputDestination outputDestination = context.getBean(OutputDestination.class);

		Message<byte[]> inputMessage = MessageBuilder.withPayload("Hello".getBytes()).build();
		inputDestination.send(inputMessage, "uppercase-in-0");
		inputDestination.send(inputMessage, "reverse-in-0");

		Message<byte[]> outputMessage = outputDestination.receive(0, "uppercase-out-0");
		assertThat(outputMessage.getPayload()).isEqualTo("HELLO".getBytes());

		outputMessage = outputDestination.receive(0, "reverse-out-0");
		assertThat(outputMessage.getPayload()).isEqualTo("olleH".getBytes());
	}
}

destination などの追加のマッピングプロパティがある場合は、それらの名前を使用する必要があります。例: uppercase 関数の入力と出力を myInput および myOutput バインディング名に明示的にマップする前述のテストの別のバージョンを検討してください。

@Test
public void testMultipleFunctions() {
	try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
			TestChannelBinderConfiguration.getCompleteConfiguration(
					SampleFunctionConfiguration.class))
							.run(
							"--spring.cloud.function.definition=uppercase;reverse",
							"--spring.cloud.stream.bindings.uppercase-in-0.destination=myInput",
							"--spring.cloud.stream.bindings.uppercase-out-0.destination=myOutput"
							)) {

		InputDestination inputDestination = context.getBean(InputDestination.class);
		OutputDestination outputDestination = context.getBean(OutputDestination.class);

		Message<byte[]> inputMessage = MessageBuilder.withPayload("Hello".getBytes()).build();
		inputDestination.send(inputMessage, "myInput");
		inputDestination.send(inputMessage, "reverse-in-0");

		Message<byte[]> outputMessage = outputDestination.receive(0, "myOutput");
		assertThat(outputMessage.getPayload()).isEqualTo("HELLO".getBytes());

		outputMessage = outputDestination.receive(0, "reverse-out-0");
		assertThat(outputMessage.getPayload()).isEqualTo("olleH".getBytes());
	}
}

テストバインダーと PollableMessageSource

Spring Integration Test Binder を使用すると、PollableMessageSource を使用するときにテストを作成することもできます(詳細については、[ ポーリングされたコンシューマーの使用 ] を参照してください)。

ただし、理解する必要がある重要なことは、ポーリングはイベント駆動型ではなく、PollableMessageSource は、メッセージ(単数)を生成(ポーリング)する操作を公開する戦略であるということです。ポーリングする頻度、使用するスレッドの数、ポーリング元(メッセージキューまたはファイルシステム)は完全にあなた次第です。言い換えると、ポーラーやスレッド、または実際のメッセージのソースを構成するのはユーザーの責任です。幸いなことに、Spring には、それを正確に構成するための抽象化がたくさんあります。

例を見てみましょう:

@Test
public void samplePollingTest() {
	ApplicationContext context = new SpringApplicationBuilder(SamplePolledConfiguration.class)
				.web(WebApplicationType.NONE)
				.run("--spring.jmx.enabled=false", "--spring.cloud.stream.pollable-source=myDestination");
	OutputDestination destination = context.getBean(OutputDestination.class);
	System.out.println("Message 1: " + new String(destination.receive().getPayload()));
	System.out.println("Message 2: " + new String(destination.receive().getPayload()));
	System.out.println("Message 3: " + new String(destination.receive().getPayload()));
}

@EnableTestBinder
@EnableAutoConfiguration
public static class SamplePolledConfiguration {
	@Bean
	public ApplicationRunner poller(PollableMessageSource polledMessageSource, StreamBridge output, TaskExecutor taskScheduler) {
		return args -> {
			taskScheduler.execute(() -> {
				for (int i = 0; i < 3; i++) {
					try {
						if (!polledMessageSource.poll(m -> {
							String newPayload = ((String) m.getPayload()).toUpperCase();
							output.send("myOutput", newPayload);
						})) {
							Thread.sleep(2000);
						}
					}
					catch (Exception e) {
						// handle failure
					}
				}
			});
		};
	}
}

上記の (非常に初歩的な) 例は、2 秒間隔で 3 つのメッセージを生成し、Source の出力先に送信します。このメッセージは、このバインダーが OutputDestination に送信し、そこで取得します (アサーションの場合)。現在、次のように出力されます。

Message 1: POLLED DATA
Message 2: POLLED DATA
Message 3: POLLED DATA

ご覧のとおり、データは同じです。これは、このバインダーが実際の MessageSource のデフォルトの実装(poll() 操作を使用してメッセージがポーリングされるソース)を定義しているためです。ほとんどのテストシナリオには十分ですが、独自の MessageSource を定義したい場合があります。これを行うには、テスト構成で型 MessageSource の Bean を構成し、メッセージソーシングの独自の実装を提供します。

次に例を示します。

@Bean
public MessageSource<?> source() {
	return () -> new GenericMessage<>("My Own Data " + UUID.randomUUID());
}

次の出力をレンダリングします。

Message 1: MY OWN DATA 1C180A91-E79F-494F-ABF4-BA3F993710DA
Message 2: MY OWN DATA D8F3A477-5547-41B4-9434-E69DA7616FEE
Message 3: MY OWN DATA 20BF2E64-7FF4-4CB6-A823-4053D30B5C74
この Bean messageSource には、無関係な理由で Spring Boot によって提供される同じ名前(異なる型)の Bean と競合するため、名前を付けないでください。