测试支持
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
实例。
有关此类的更多信息,请参阅 Javadoc 中的其他 TestUtils
方法。
使用 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 测试框架。由于 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 框架,并与之兼容。(当前 Mockito 传递依赖项为 2.5.x 或更高版本。)
MockIntegration
MockIntegration
工厂提供了一个 API 来构建 Spring Integration bean 的模拟,这些 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,它允许您在被测端点中用 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
示例。