测试支持
测试支持
Spring 集成提供了许多实用程序和注释来帮助您测试应用程序。 测试支持由两个模块提供:
-
spring-integration-test-support
包含核心项和共享实用程序 -
spring-integration-test
为集成测试提供 mocking 和 application context configuration 组件
spring-integration-test-support
(spring-integration-test
在 5.0 之前的版本中)为单元测试提供基本的独立实用程序、规则和匹配器。
(它也不依赖于 Spring 集成本身,并在 Framework 测试内部使用)。spring-integration-test
旨在帮助进行集成测试,并提供全面的高级 API 来模拟集成组件并验证单个组件的行为,包括整个集成流或仅部分集成流。
对企业中测试的全面处理超出了本参考手册的范围。 请参阅 Gregor Hohpe 和 Wendy Istvanick 撰写的 “Test-Driven Development in Enterprise Integration Projects” 论文,了解测试目标集成解决方案的思路和原则。
Spring 集成测试框架和测试工具完全基于现有的 JUnit、Hamcrest 和 Mockito 库。 应用程序上下文交互基于 Spring 测试框架。 有关更多信息,请参阅这些项目的文档。
感谢 Spring Integration Framework 中 EIP 的规范实现及其一等公民(例如MessageChannel
,Endpoint
和MessageHandler
)、抽象和松散耦合原则,您可以实施任何复杂程度的集成解决方案。
使用流定义的 Spring 集成 API,您可以改进、修改甚至替换流的某些部分,而不会影响(大部分)集成解决方案中的其他组件。
测试这样的集成解决方案仍然是一个挑战,无论是从端到端方法还是从隔离方法。
一些现有的工具可以帮助测试或模拟一些集成协议,并且它们与 Spring 集成通道适配器配合得很好。
此类工具的示例包括:
-
Spring
MockMVC
及其MockRestServiceServer
可用于测试 HTTP。 -
一些 RDBMS 供应商提供用于 JDBC 或 JPA 支持的嵌入式数据库。
-
可以嵌入 ActiveMQ 以测试 JMS 或 STOMP 协议。
-
有用于嵌入式 MongoDB 和 Redis 的工具。
-
Tomcat 和 Jetty 具有嵌入式库,用于测试真实的 HTTP、Web 服务或 WebSockets。
-
这
FtpServer
和SshServer
可用于测试 FTP 和 SFTP 协议。 -
Hazelcast 可以在测试中作为真实数据网格节点运行。
-
Curator Framework 提供了一个
TestingServer
进行 Zookeeper 交互。 -
Apache Kafka 提供了管理工具,用于将 Kafka 代理嵌入到测试中。
-
GreenMail 是一个开源、直观且易于使用的电子邮件服务器测试套件,用于测试目的。
这些工具和库中的大多数都用于 Spring 集成测试。
此外,从 GitHub 存储库(在test
目录中),您可以发现有关如何为集成解决方案构建自己的测试的想法。
本章的其余部分描述了 Spring 集成提供的测试工具和实用程序。
测试实用程序
这spring-integration-test-support
module 为单元测试提供 Utilities 和 Helpers。
TestUtils
这TestUtils
class 主要用于 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
并提供从 Target 私有属性获取值的功能。
如前面的示例所示,它还支持使用点分表示法访问嵌套属性。
这createTestApplicationContext()
Factory 方法会生成一个TestApplicationContext
实例。
用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 测试规则用于指示是否应在以下情况下运行 testRUN_LONG_INTEGRATION_TESTS
environment 或 system 属性设置为true
.
否则,将跳过该选项。
出于同样的原因,从 5.1 版本开始,@LongRunningTest
为 JUnit 5 测试提供了条件注释。
Hamcrest 和 Mockito 匹配器
这org.springframework.integration.test.matcher
包包含多个Matcher
实现来断言Message
及其属性。
以下示例演示如何使用一个这样的匹配器 (PayloadMatcher
):
@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
factory 可用于 stub 和验证的 mocks,如下例所示:
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
package 提供了一些组件,用于增强测试上下文以满足集成需求。
首先,我们使用@SpringIntegrationTest
注解来启用 Spring 集成测试框架,如下例所示:
@SpringJUnitConfig
@SpringIntegrationTest(noAutoStartup = {"inboundChannelAdapter", "*Source*"})
public class MyIntegrationTests {
@Autowired
private MockIntegrationContext mockIntegrationContext;
}
这@SpringIntegrationTest
annotation 填充MockIntegrationContext
bean,您可以将其自动连接到测试类以访问其方法。
使用noAutoStartup
选项中,Spring 集成测试框架会阻止通常autoStartup=true
从开始。
端点与提供的模式匹配,这些模式支持以下简单的模式样式:xxx*
,xxx
,*xxx
和xxx*yyy
.
当我们不想从入站通道适配器(例如 AMQP 入站网关、JDBC 轮询通道适配器、客户端模式下的 WebSocket 消息生产者等)与目标系统建立真正的连接时,这很有用。
这MockIntegrationContext
旨在用于目标测试用例,以便在实际应用程序上下文中对 bean 进行修改。
例如,具有autoStartup
overridden 设置为false
可以替换为 mocks,如下例所示:
@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 为此,我们将 RealMessageSource 使用我们的 mock。
同样,MockIntegrationContext.substituteMessageHandlerFor() 需要一个 bean 名称IntegrationConsumer ,它将MessageHandler 作为终端节点。 |
执行测试后,您可以使用MockIntegrationContext.resetBeans()
:
@After
public void tearDown() {
this.mockIntegrationContext.resetBeans();
}
有关更多信息,请参阅 Javadoc。
集成模拟
这org.springframework.integration.test.mock
package 提供了用于模拟、存根和验证 Spring 集成组件上的活动的工具和实用程序。
模拟功能完全基于著名的 Mockito 框架并与之兼容。
(当前的 Mockito 传递依赖项版本为 2.5.x 或更高版本。
Mock集成
这MockIntegration
factory 提供了一个 API 来为 Spring 集成 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
mock 对象、MockMessageHandler
是常规的AbstractMessageProducingHandler
扩展,用于对传入消息进行存根处理。
这MockMessageHandler
提供handleNext(Consumer<Message<?>>)
为下一条请求消息指定单向存根。
它用于模拟不生成回复的消息处理程序。
这handleNextAndReply(Function<Message<?>, ?>)
用于对下一条请求消息执行相同的存根逻辑并为其生成回复。
它们可以链接起来以模拟所有预期请求消息变体的任何任意请求-回复场景。
这些使用者和函数将应用于传入消息,一次一个地从堆栈中应用于传入消息,直到最后一条消息,然后用于所有剩余消息。
该行为类似于 MockitoAnswer
或doReturn()
应用程序接口。
此外,您可以提供 MockitoArgumentCaptor<Message<?>>
到MockMessageHandler
在 constructor 参数中。
每个请求消息的MockMessageHandler
被该ArgumentCaptor
.
在测试期间,您可以使用其getValue()
和getAllValues()
方法来验证和断言这些请求消息。
这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());
常规的MessageHandler mocking(或MockMessageHandler ) 甚至必须用于ReactiveStreamsConsumer 替换为ReactiveMessageHandler 配置。 |
请参阅MockIntegration
和MockMessageHandler
Javadoc 了解更多信息。
其他资源
除了探索框架本身中的测试用例外,Spring 集成示例存储库还提供了一些专门用于演示测试的示例应用程序,例如testing-examples
和advanced-testing-examples
.
在某些情况下,样本本身具有全面的端到端测试,例如file-split-ftp
样本。