|
此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Integration 7.0.4! |
测试支持
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 撰写的《企业集成项目中的测试驱动开发》("Test-Driven Development in Enterprise Integration Projects")一文,以获取针对目标集成解决方案进行测试的灵感与原则来源。
Spring 集成测试框架和测试工具完全基于现有的 JUnit、Hamcrest 和 Mockito 库。 应用上下文交互基于 Spring 测试框架。 有关这些项目的更多信息,请参阅其文档。
得益于 EIP 在 Spring Integration Framework 中的规范实现及其一等公民(例如 MessageChannel、Endpoint 和 MessageHandler)、抽象层以及松耦合原则,您可以实现任何复杂度的集成解决方案。
通过用于流定义的 Spring Integration API,您可以在不影响(大多数情况下)集成解决方案中其他组件的情况下,改进、修改甚至替换流的某些部分。
测试此类集成解决方案仍然是一个挑战,无论是从端到端的角度还是从独立隔离的角度来看。
现有的几种工具可以帮助测试或模拟某些集成协议,并且它们与 Spring Integration 通道适配器配合良好。
此类工具的示例如下:
-
Spring
MockMVC及其MockRestServiceServer可用于 HTTP 测试。 -
一些 RDBMS 提供商为 JDBC 或 JPA 支持提供了嵌入式数据库。
-
ActiveMQ 可以嵌入以测试 JMS 或 STOMP 协议。
-
There are tools for embedded MongoDB and Redis.
-
Tomcat 和 Jetty 都内置了用于测试真实 HTTP、Web Services 或 WebSockets 的库。
-
Apache Mina 项目中的
FtpServer和SshServer可用于测试 FTP 和 SFTP 协议。 -
Hazelcast 可以在测试中作为真实的数据网格节点运行。
-
The Curator Framework provides a
TestingServerfor Zookeeper interaction. -
Apache Kafka 提供了管理工具,以便在测试中嵌入 Kafka Broker。
-
GreenMail 是一个开源的、直观且易于使用的电子邮件服务器测试套件,专用于测试目的。
这些工具和库大多用于 Spring 集成测试。
此外,您还可以从 GitHub 仓库(每个模块的test目录中)发现如何构建您自己的集成解决方案测试的思路。
本章其余部分介绍了 Spring Integration 提供的测试工具和实用程序。
测试工具
The spring-integration-test-support 模块为单元测试提供了实用工具和辅助函数。
测试工具
The TestUtils 类主要用于 JUnit 测试中的属性断言,如下例所示:
@Test
public void loadBalancerRef() {
MessageChannel channel = channels.get("lbRefChannel");
LoadBalancingStrategy lbStrategy = TestUtils.getPropertyValue(channel, "dispatcher.loadBalancingStrategy");
assertTrue(lbStrategy instanceof SampleLoadBalancingStrategy);
}
TestUtils.getPropertyValue() 基于 Spring 的 DirectFieldAccessor,提供从目标私有属性获取值的能力。
如前例所示,它还支持通过点号表示法访问嵌套属性。
此方法还带有一个泛型参数,用于在测试逻辑中将提取的值灵活地转换为预期类型。
The 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 条件
The @LongRunningTest 条件注解用于指示当 RUN_LONG_INTEGRATION_TESTS 环境或系统属性设置为 true 时是否应运行测试。
否则,该测试将被跳过。
Hamcrest 和 Mockito 匹配器
The 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)));
}
The 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 集成与测试上下文
通常,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 集成测试框架会阻止通常处于 autoStartup=true 状态的端点启动。
这些端点会根据提供的模式进行匹配,该模式支持以下简单的模式样式:xxx*、xxx、*xxx 以及 xxx*yyy。
当我们希望入站通道适配器(例如 AMQP 入站网关、JDBC轮询通道适配器、客户端模式的WebSocket消息生产者等)不建立与目标系统的真实连接时,这将非常有用。
The @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);
}
The mySourceEndpoint refers here to the bean name of the SourcePollingChannelAdapter for which we replace the real MessageSource with our mock.
Similarly, the MockIntegrationContext.substituteMessageHandlerFor() expects a bean name for the IntegrationConsumer, which wraps a MessageHandler as an endpoint. |
测试执行后,您可以使用 MockIntegrationContext.resetBeans() 将端点 Bean 的状态恢复为实际配置:
@AfterEach
public void tearDown() {
this.mockIntegrationContext.resetBeans();
}
从 6.3 版本开始,引入了 MockIntegrationContext.substituteTriggerFor() API。
此 API 可用于替换 AbstractPollingEndpoint 中的真实 Trigger。
例如,生产环境的配置可能依赖于每日(甚至每周)的 cron 调度任务。
任何自定义的 Trigger 都可以注入到目标端点,以缩短时间跨度。
例如,上述提到的 OnlyOnceTrigger 建议的行为是立即调度轮询任务,并且仅执行一次。
有关更多信息,请参阅javadoc。
集成模拟
The org.springframework.integration.test.mock 包提供了用于模拟、存根和验证 Spring Integration 组件活动的工具和实用程序。
模拟功能完全基于并兼容著名的 Mockito 框架。
(当前的 Mockito 传递依赖项版本为 2.5.x 或更高版本。)
模拟集成
The MockIntegration factory 提供了用于构建 Spring Integration 集成流中组成部分(MessageSource、MessageProducer、MessageHandler和MessageChannel)的 Mock 对象的 API。
您可以在配置阶段以及目标测试方法中使用目标 Mock 对象,在执行验证和断言之前替换真实的端点,如下例所示:
<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 用于处理传入消息的存根(stub)逻辑。
MockMessageHandler 提供了 handleNext(Consumer<Message<?>>),用于为下一个请求消息指定单向存根。
它用于模拟那些不产生回复的消息处理器。
handleNextAndReply(Function<Message<?>, ?>) 可用于对下一个请求消息执行相同的存根逻辑,并为其生成回复。
它们可以链式组合,以模拟所有预期请求消息变体的任意请求 - 回复场景。
这些消费者和函数将按顺序从栈中依次应用于传入消息,直到最后一个;该最后一个函数将用于处理所有剩余的消息。
其行为类似于 Mockito 的 Answer 或 doReturn() API。
此外,您还可以在构造参数中将 Mockito ArgumentCaptor<Message<?>> 提供给 MockMessageHandler。
每个针对 MockMessageHandler 的请求消息都会被该 ArgumentCaptor 捕获。
在测试期间,您可以使用其 getValue() 和 getAllValues() 方法来验证和断言这些请求消息。
The 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 示例仓库还提供了一些专门用于展示测试的示例应用程序,例如 testing-examples 和 advanced-testing-examples。
在某些情况下,示例本身包含全面的端到端测试,例如 file-split-ftp 示例。