測試支援
Spring Integration 提供了許多實用工具和註解來幫助您測試應用程式。測試支援由兩個模組提供
-
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 測試框架。有關這些專案的更多資訊,請參閱其文件。
得益於 Spring Integration 框架中 EIP 的規範實現及其一流的“公民”(例如 MessageChannel、Endpoint 和 MessageHandler)、抽象和松耦合原則,您可以實現任何複雜性的整合解決方案。藉助用於流定義的 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 可以在測試中作為真實資料網格節點執行。
-
Curator 框架為 Zookeeper 互動提供了
TestingServer。 -
Apache Kafka 提供了管理工具,可在測試中嵌入 Kafka Broker。
-
GreenMail 是一個開源、直觀且易於使用的電子郵件伺服器測試套件,用於測試目的。
這些工具和庫中的大部分都用於 Spring Integration 測試。此外,您可以從 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 對於輪詢端點很有用,當您只需要生成一條測試訊息並驗證其行為而不會影響其他週期性訊息時。以下示例展示瞭如何配置 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();
...
}
JUnit 條件
@LongRunningTest 條件註解用於指示是否應該在設定 RUN_LONG_INTEGRATION_TESTS 環境變數或系統屬性為 true 時執行測試。否則,它將被跳過。
Hamcrest 和 Mockito 匹配器
org.springframework.integration.test.matcher 包包含幾個 Matcher 實現,用於在單元測試中斷言 Message 及其屬性。以下示例展示瞭如何使用其中一個匹配器 (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));
Spring Integration 和測試上下文
通常,Spring 應用程式的測試使用 Spring Test Framework。由於 Spring Integration 基於 Spring Framework 基礎,因此我們使用 Spring Test Framework 所能做的一切也適用於測試整合流。org.springframework.integration.test.context 包提供了一些元件,用於增強整合需求的測試上下文。首先,我們使用 @SpringIntegrationTest 註解配置我們的測試類以啟用 Spring Integration Test Framework,如以下示例所示
@SpringJUnitConfig
@SpringIntegrationTest(noAutoStartup = {"inboundChannelAdapter", "*Source*"})
public class MyIntegrationTests {
@Autowired
private MockIntegrationContext mockIntegrationContext;
}
@SpringIntegrationTest 註解填充了一個 MockIntegrationContext bean,您可以將其自動裝配到測試類以訪問其方法。透過 noAutoStartup 選項,Spring Integration Test Framework 阻止通常 autoStartup=true 的端點啟動。端點與提供的模式匹配,這些模式支援以下簡單模式樣式:xxx*、xxx、*xxx 和 xxx*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 指的是 SourcePollingChannelAdapter 的 bean 名稱,我們用模擬替換了它的真實 MessageSource。類似地,MockIntegrationContext.substituteMessageHandlerFor() 期望 IntegrationConsumer 的 bean 名稱,它將 MessageHandler 包裝為端點。 |
執行測試後,您可以使用 MockIntegrationContext.resetBeans() 將端點 bean 的狀態恢復到真實配置
@AfterEach
public void tearDown() {
this.mockIntegrationContext.resetBeans();
}
從版本 6.3 開始,引入了 MockIntegrationContext.substituteTriggerFor() API。這可用於替換 AbstractPollingEndpoint 中的真實 Trigger。例如,生產配置可能依賴於每日(甚至每週)的 cron 計劃。任何自定義 Trigger 都可以注入到目標端點以緩解時間跨度。例如,上面提到的 OnlyOnceTrigger 建議立即安排輪詢任務並只執行一次的行為。
有關更多資訊,請參閱 Javadoc。
整合模擬
org.springframework.integration.test.mock 包提供了用於模擬、存根和驗證 Spring Integration 元件上活動的工具和實用工具。模擬功能完全基於併兼容著名的 Mockito Framework。(當前的 Mockito 傳遞依賴項是 2.5.x 或更高版本。)
MockIntegration
MockIntegration 工廠提供了一個 API,用於為作為整合流一部分的 Spring Integration bean(MessageSource、MessageProducer、MessageHandler 和 MessageChannel)構建模擬。您可以在配置階段以及在目標測試方法中使用目標模擬來替換實際端點,然後再執行驗證和斷言,如以下示例所示
<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 是一個常規的 AbstractMessageProducingHandler 擴充套件,帶有一個鏈式 API,用於對傳入訊息進行存根處理。MockMessageHandler 提供了 handleNext(Consumer<Message<?>>) 來為下一個請求訊息指定一個單向存根。它用於模擬不產生回覆的訊息處理程式。handleNextAndReply(Function<Message<?>, ?>) 用於對下一個請求訊息執行相同的存根邏輯併為其生成回覆。它們可以鏈式呼叫以模擬所有預期請求訊息變體的任意請求-回覆場景。這些消費者和函式按順序(從堆疊中)應用於傳入訊息,直到最後一個,然後將其用於所有剩餘的訊息。此行為類似於 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());
即使對於帶有 ReactiveMessageHandler 配置的 ReactiveStreamsConsumer,也必須使用常規的 MessageHandler 模擬(或 MockMessageHandler)。 |
有關更多資訊,請參閱 MockIntegration 和 MockMessageHandler Javadoc。
其他資源
除了探索框架本身的測試用例外,Spring Integration Samples 儲存庫還包含一些專門用於展示測試的示例應用程式,例如 testing-examples 和 advanced-testing-examples。在某些情況下,這些示例本身具有全面的端到端測試,例如 file-split-ftp 示例。