测试支持
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 框架提供了一个
TestingServer
用于与 Zookeeper 交互。 -
Apache Kafka 提供了管理工具,可以在测试中嵌入 Kafka Broker。
-
GreenMail 是一个开源的、直观的、易于使用的电子邮件服务器测试套件,用于测试目的。
大多数这些工具和库都用于 Spring 集成测试。此外,从 GitHub 仓库(在每个模块的 test
目录中),您可以发现有关如何构建自己的集成解决方案测试的想法。
本章的其余部分介绍了 Spring 集成提供的测试工具和实用程序。
测试实用程序
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 集成环境生成 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 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 消息生产者等)避免与目标系统的真实连接时非常有用。
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 名称,我们用 mock 对象替换了真实的 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 示例存储库 还有一些专门用于展示测试的示例应用程序,例如 testing-examples
和 advanced-testing-examples
。在某些情况下,示例本身具有全面的端到端测试,例如 file-split-ftp
示例。