測試支援
Spring Integration 提供了許多實用工具和註解來協助您測試應用程式。測試支援由兩個模組提供
-
spring-integration-test-support
包含核心項目和共用實用工具 -
spring-integration-test
為整合測試提供模擬和應用程式內容設定元件
spring-integration-test-support
(5.0 之前的版本為 spring-integration-test
) 為單元測試提供基本、獨立的實用工具、規則和匹配器。(它也沒有對 Spring Integration 本身的依賴性,並在 Framework 測試中內部使用)。spring-integration-test
旨在協助整合測試,並提供全面的高階 API 來模擬整合元件並驗證個別元件的行為,包括整個整合流程或僅部分流程。
關於企業中測試的詳盡論述已超出本參考手冊的範圍。請參閱 Gregor Hohpe 和 Wendy Istvanick 的 「企業整合專案中的測試驅動開發」 文件,以取得測試目標整合解決方案的想法和原則來源。
Spring Integration Test Framework 和測試實用工具完全基於現有的 JUnit、Hamcrest 和 Mockito 程式庫。應用程式內容互動基於 Spring 測試框架。如需更多資訊,請參閱這些專案的文件。
由於 Spring Integration Framework 中 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 Services 或 WebSockets。
-
Apache Mina 專案的
FtpServer
和SshServer
可用於測試 FTP 和 SFTP 協定。 -
Hazelcast 可以在測試中作為真實資料網格節點執行。
-
Curator Framework 為 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 規則和條件
LongRunningIntegrationTest
JUnit 4 測試規則用於指示是否應在 RUN_LONG_INTEGRATION_TESTS
環境或系統屬性設定為 true
時執行測試。否則,將會略過。由於相同原因,自版本 5.1 起,為 JUnit 5 測試提供 @LongRunningTest
條件式註解。
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 的狀態還原為真實設定
@After
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
是具有鏈 API 的常規 AbstractMessageProducingHandler
擴充功能,用於存根處理傳入訊息。MockMessageHandler
提供 handleNext(Consumer<Message<?>>)
,以指定下一個請求訊息的單向存根。它用於模擬不產生回覆的訊息處理器。handleNextAndReply(Function<Message<?>, ?>)
用於對下一個請求訊息執行相同的存根邏輯並為其產生回覆。它們可以鏈結在一起,以模擬所有預期請求訊息變體的任何任意請求-回覆案例。這些消費者和函數會一次一個地應用於傳入訊息,從堆疊中取出,直到最後一個,然後用於所有剩餘訊息。此行為類似於 Mockito Answer
或 doReturn()
API。
此外,您可以將 Mockito ArgumentCaptor<Message<?>>
以建構子引數的形式提供給 MockMessageHandler
。MockMessageHandler
的每個請求訊息都會由該 ArgumentCaptor
擷取。在測試期間,您可以使用其 getValue()
和 getAllValues()
方法來驗證和判斷提示這些請求訊息。
MockIntegrationContext
提供 substituteMessageHandlerFor()
API,可讓您在受測端點中,使用 MockMessageHandler
取代實際設定的 MessageHandler
。
下列範例顯示典型的使用案例
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
範例。