測試支援
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 測試框架和測試實用工具完全基於現有的 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 Rules 和 條件
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 測試框架。由於 Spring Integration 基於 Spring Framework 基礎,因此使用 Spring 測試框架所能做到的一切也都適用於測試整合流。org.springframework.integration.test.context
包提供了一些元件,用於增強整合測試所需的測試上下文。首先,我們使用 @SpringIntegrationTest
註解配置測試類以啟用 Spring Integration 測試框架,如下例所示:
@SpringJUnitConfig
@SpringIntegrationTest(noAutoStartup = {"inboundChannelAdapter", "*Source*"})
public class MyIntegrationTests {
@Autowired
private MockIntegrationContext mockIntegrationContext;
}
@SpringIntegrationTest
註解填充了一個 MockIntegrationContext
bean,你可以將其自動注入到測試類中以訪問其方法。透過 noAutoStartup
選項,Spring Integration 測試框架會阻止通常 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 Configuration 來實現與上例相同的配置:
@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<?>, ?>)
用於對下一個請求訊息執行相同的存根邏輯併為其生成回覆。它們可以鏈式呼叫,以模擬所有預期請求訊息變體的任意請求-回覆場景。這些 consumer 和 function 會按順序應用於傳入訊息,直到最後一個,然後該最後一個用於所有剩餘訊息。這種行為類似於 Mockito 的 Answer
或 doReturn()
API。
此外,你可以在建構函式引數中向 MockMessageHandler
提供一個 Mockito ArgumentCaptor<Message<?>>
。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
示例。