Testing support
Spring Integration 提供了大量的实用工具和注释,帮助你测试应用程序。测试支持由两个模块提供:
Spring Integration provides a number of utilities and annotations to help you test your application. Testing support is presented by two modules:
-
spring-integration-test-support
contains core items and shared utilities -
spring-integration-test
provides mocking and application context configuration components for integration tests
spring-integration-test-support
(在 5.0 之前的版本中为 spring-integration-test
)为单元测试提供了基本的独立实用工具、规则和匹配器。(它也不依赖于 Spring Integration 本身,并在框架测试中被内部使用)。spring-integration-test
旨在帮助进行集成测试,并提供一个综合的高级 API,以模拟集成组件,并验证独立组件(包括整个集成流或其中的一部分)的行为。
spring-integration-test-support
(spring-integration-test
in versions before 5.0) provides basic, standalone utilities, rules, and matchers for unit testing.
(it also has no dependencies on Spring Integration itself and is used internally in Framework tests).
spring-integration-test
aims to help with integration testing and provides a comprehensive high-level API to mock integration components and verify the behavior of individual components, including whole integration flows or only parts of them.
在企业中彻底处理测试超出了本参考手册的范围。请参阅 Gregor Hohpe 和 Wendy Istvanick 撰写的 “Test-Driven Development in Enterprise Integration Projects” 论文,获取有关如何测试您的目标集成解决方案的想法和原则。
A thorough treatment of testing in the enterprise is beyond the scope of this reference manual. See the “Test-Driven Development in Enterprise Integration Projects” paper, by Gregor Hohpe and Wendy Istvanick, for a source of ideas and principles for testing your target integration solution.
Spring Integration 测试框架和测试实用程序完全基于现有的 JUnit、Hamcrest 和 Mockito 库。应用程序上下文交互基于 Spring test framework。请参阅这些项目的文档以获取更多信息。
The Spring Integration Test Framework and test utilities are fully based on existing JUnit, Hamcrest, and Mockito libraries. The application context interaction is based on the Spring test framework. See the documentation for those projects for further information.
由于 Spring Integration 框架中 EIP 和其一类公民(如 MessageChannel
、Endpoint
和 MessageHandler
)的规范实现、抽象以及松散耦合原则,你可以实现任何复杂性的集成解决方案。使用 Spring Integration API 进行流定义,你可以在不影响(大部分)集成解决方案中其他组件的情况下,对流程的某个部分进行改进、修改甚至替换。测试此类集成解决方案仍然是一个挑战,无论是从端到端方法还是隔离方法。一些现有的工具可以帮助测试或模拟某些集成协议,并且它们与 Spring Integration 通道适配器配合得很好。这些工具的示例包括:
Thanks to the canonical implementation of the EIP in Spring Integration Framework and its first-class citizens (such as MessageChannel
, Endpoint
and MessageHandler
), abstractions, and loose coupling principles, you can implement integration solutions of any complexity.
With the Spring Integration API for the flow definitions, you can improve, modify or even replace some part of the flow without impacting (mostly) other components in the integration solution.
Testing such an integration solution is still a challenge, both from an end-to-end approach and from an in-isolation approach.
Several existing tools can help to test or mock some integration protocols, and they work well with Spring Integration channel adapters.
Examples of such tools include the following:
-
Spring
MockMVC
and itsMockRestServiceServer
can be used for testing HTTP. -
Some RDBMS vendors provide embedded data bases for JDBC or JPA support.
-
ActiveMQ can be embedded for testing JMS or STOMP protocols.
-
There are tools for embedded MongoDB and Redis.
-
Tomcat and Jetty have embedded libraries to test real HTTP, Web Services, or WebSockets.
-
The
FtpServer
andSshServer
from the Apache Mina project can be used for testing the FTP and SFTP protocols. -
Hazelcast can be run as real-data grid nodes in the tests.
-
The Curator Framework provides a
TestingServer
for Zookeeper interaction. -
Apache Kafka provides admin tools to embed a Kafka Broker in the tests.
-
The GreenMail is an open source, intuitive and easy-to-use test suite of email servers for testing purposes.
大多数这些工具和库都用于 Spring Integration 测试。此外,从 GitHub repository(在每个模块的 test
目录中),您可以发现如何针对集成解决方案构建自己的教程。
Most of these tools and libraries are used in Spring Integration tests.
Also, from the GitHub repository (in the test
directory of each module), you can discover ideas for how to build your own tests for integration solutions.
本章的其余部分描述了 Spring Integration 提供的测试工具和实用工具。
The rest of this chapter describes the testing tools and utilities provided by Spring Integration.
Testing Utilities
spring-integration-test-support
模块为单元测试提供了实用工具和帮助程序。
The spring-integration-test-support
module provides utilities and helpers for unit testing.
TestUtils
TestUtils
类主要用于 JUnit 测试中的属性断言,如下例所示:
The TestUtils
class is mostly used for properties assertions in JUnit tests, as the following example shows:
@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
,并提供从目标私有属性获取值的能力。如前例所示,它还支持通过使用点号符号访问嵌套属性。
TestUtils.getPropertyValue()
is based on Spring’s DirectFieldAccessor
and provides the ability to get a value from the target private property.
As shown in the preceding example, it also supports nested properties access by using dotted notation.
createTestApplicationContext()
工厂方法使用提供的 Spring Integration 环境生成 TestApplicationContext
实例。
The createTestApplicationContext()
factory method produces a TestApplicationContext
instance with the supplied Spring Integration environment.
请参阅此类的其他 TestUtils
方法的 Javadoc,以获取有关此类的更多信息。
See the Javadoc of other TestUtils
methods for more information about this class.
Using OnlyOnceTrigger
当您只需要生成一个测试消息并在不影响其他周期性消息的情况下验证行为时, OnlyOnceTrigger
对于轮询端点很有用。以下示例显示了如何配置 OnlyOnceTrigger
:
OnlyOnceTrigger
is useful for polling endpoints when you need to produce only one test message and verify the behavior without impacting other period messages.
The following example shows how to configure 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
的上述配置:
The following example shows how to use the preceding configuration of OnlyOnceTrigger
for testing:
@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();
...
}
Support Components
org.springframework.integration.test.support
包中包含各种抽象类,你应在目标测试中实现它们。
The org.springframework.integration.test.support
package contains various abstract classes that you should implement in target tests
JUnit Rules and Conditions
如果将测试环境或系统属性 RUN_LONG_INTEGRATION_TESTS
设置为 true
,则 JUnit 4 测试规则 LongRunningIntegrationTest
现在指示是否应运行测试。否则,将跳过它。从 5.1 版开始,出于同样的原因,为 JUnit 5 测试提供了 @LongRunningTest
条件注释。
The LongRunningIntegrationTest
JUnit 4 test rule is present to indicate if test should be run if RUN_LONG_INTEGRATION_TESTS
environment or system property is set to true
.
Otherwise, it is skipped.
For the same reason since version 5.1, a @LongRunningTest
conditional annotation is provided for JUnit 5 tests.
Hamcrest and Mockito Matchers
org.springframework.integration.test.matcher
包包含多个 Matcher
实现,以便在单元测试中断言 Message
及其属性。以下示例演示了如何使用一个此类匹配器 (PayloadMatcher
):
The org.springframework.integration.test.matcher
package contains several Matcher
implementations to assert Message
and its properties in unit tests.
The following example shows how to use one such matcher (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
工厂用于存根和验证的模拟,如下例所示:
The MockitoMessageMatchers
factory can be used for mocks for stubbing and verifications, as the following example shows:
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));
AssertJ conditions and predicates
从 5.2 版开始,引入了 MessagePredicate
,可在 AssertJ matches()
断言中使用。它需要一个 Message
对象作为期望。此外,还可以针对排除在期望之外的以及需要断言的实际消息中的标头进行配置。
Starting with version 5.2, the MessagePredicate
is introduced to be used in the AssertJ matches()
assertion.
It requires a Message
object as an expectation.
And also ot can be configured with headers to exclude from expectation as well as from actual message to assert.
Spring Integration and the Test Context
通常,用于 Spring 应用程序的测试会使用 Spring 测试框架。由于 Spring Integration 基于 Spring 框架基础,因此我们在使用 Spring 测试框架时可以做的一切在集成流测试中也适用。org.springframework.integration.test.context
包提供了一些组件来增强针对集成需求的测试上下文。首先,我们使用 @SpringIntegrationTest
注解配置我们的测试类,以启用 Spring Integration 测试框架,如下例所示:
Typically, tests for Spring applications use the Spring Test Framework.
Since Spring Integration is based on the Spring Framework foundation, everything we can do with the Spring Test Framework also applies when testing integration flows.
The org.springframework.integration.test.context
package provides some components for enhancing the test context for integration needs.
First we configure our test class with a @SpringIntegrationTest
annotation to enable the Spring Integration Test Framework, as the following example shows:
@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
。
The @SpringIntegrationTest
annotation populates a MockIntegrationContext
bean, which you can autowire to the test class to access its methods.
With the noAutoStartup
option, the Spring Integration Test Framework prevents endpoints that are normally autoStartup=true
from starting.
The endpoints are matched to the provided patterns, which support the following simple pattern styles: xxx*
, xxx
, *xxx
, and xxx*yyy
.
当我们希望不与入站通道适配器(例如 AMQP 入站网关、JDBC 轮询通道适配器、客户端模式中的 WebSocket 消息生产者等)建立到目标系统的实际连接时,这将非常有用。
This is useful when we would like to not have real connections to the target systems from inbound channel adapters (for example an AMQP Inbound Gateway, JDBC Polling Channel Adapter, WebSocket Message Producer in client mode, and so on).
MockIntegrationContext
应在目标测试用例中使用,以修改真实应用程序上下文中的 Bean。例如,可以将已将 autoStartup
覆盖为 false
的端点替换为模拟,如下例所示:
The MockIntegrationContext
is meant to be used in the target test cases for modifications to beans in the real application context.
For example, endpoints that have autoStartup
overridden to false
can be replaced with mocks, as the following example shows:
@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 |
在执行测试后,可以使用 MockIntegrationContext.resetBeans()
恢复端点 Bean 的状态为真实配置:
After test is performed you can restore the state of endpoint beans to the real configuration using MockIntegrationContext.resetBeans()
:
@After
public void tearDown() {
this.mockIntegrationContext.resetBeans();
}
从版本 6.3 开始,引入了 MockIntegrationContext.substituteTriggerFor()
API。这可用于替换 AbstractPollingEndpoint
中的真实 Trigger
。例如,生产配置可能依赖于每天(甚至每周)的 Cron 计划。任何自定义 Trigger
都可以注入到目标端点,以减轻时间跨度。例如,上面提到的 <<`OnlyOnceTrigger`,using-onlyoncetrigger>> 建议采取一种行为,即立即安排轮询任务并且仅执行一次。
Starting with version 6.3, the MockIntegrationContext.substituteTriggerFor()
API has been introduced.
This can be used to replace the real Trigger
in the AbstractPollingEndpoint
.
For example the production configuration may rely on daily (or even weekly) cron schedule.
Any custom Trigger
can be injected into the target endpoint to mitigate the time span.
For example, the mentioned above <<`OnlyOnceTrigger`,using-onlyoncetrigger>> suggests a behavior to schedule polling task immediately and do that only once.
有关更多信息,请参阅 Javadoc。
See the Javadoc for more information.
Integration Mocks
org.springframework.integration.test.mock
包为 Spring Integration 组件的活动提供模拟、存根和验证的工具和实用程序。模拟功能完全基于备受认可的 Mockito 框架,并且与此框架兼容。(当前 Mockito 传递依赖项采用 2.5.x 或更高版本。)
The org.springframework.integration.test.mock
package offers tools and utilities for mocking, stubbing, and verification of activity on Spring Integration components.
The mocking functionality is fully based on and compatible with the well known Mockito Framework.
(The current Mockito transitive dependency is on version 2.5.x or higher.)
MockIntegration
MockIntegration
工厂提供了一个 API 来构建 Spring Integration Bean 的模拟,这些 Bean 是集成流(MessageSource
、MessageProducer
、MessageHandler
和 MessageChannel
)的一部分。你可以在配置阶段以及目标测试方法中使用目标模拟,在执行验证和断言之前替换真实端点,如下例所示:
The MockIntegration
factory provides an API to build mocks for Spring Integration beans that are parts of the integration flow (MessageSource
, MessageProducer
, MessageHandler
, and MessageChannel
).
You can use the target mocks during the configuration phase as well as in the target test method to replace the real endpoints before performing verifications and assertions, as the following example shows:
<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 配置实现与前一个示例相同的配置:
The following example shows how to use Java Configuration to achieve the same configuration as the preceding example:
@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
,如下例所示:
For this purpose, the aforementioned MockIntegrationContext
should be used from the test, as the following example shows:
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
是一个带有链 API 的常规 AbstractMessageProducingHandler
扩展,用于存根传入消息的处理。MockMessageHandler
提供 handleNext(Consumer<Message<?>>)
来为下一个请求消息指定单向存根。它用于模拟不产生回复的消息处理器。handleNextAndReply(Function<Message<?>, ?>)
提供用于对下一个请求消息执行相同存根逻辑并生成其回复。可以对它们进行链接,以模拟针对所有预期请求消息变体的所有任意的请求-回复场景。这些使用者和函数将应用于传入消息(一次一个),知道最后一个使用者或函数,然后将其用于所有剩余消息。其行为类似于 Mockito Answer
或 doReturn()
API。
Unlike the Mockito MessageSource
mock object, the MockMessageHandler
is a regular AbstractMessageProducingHandler
extension with a chain API to stub handling for incoming messages.
The MockMessageHandler
provides handleNext(Consumer<Message<?>>)
to specify a one-way stub for the next request message.
It is used to mock message handlers that do not produce replies.
The handleNextAndReply(Function<Message<?>, ?>)
is provided for performing the same stub logic for the next request message and producing a reply for it.
They can be chained to simulate any arbitrary request-reply scenarios for all expected request messages variants.
These consumers and functions are applied to the incoming messages, one at a time from the stack, until the last, which is then used for all remaining messages.
The behavior is similar to the Mockito Answer
or doReturn()
API.
此外,你可以向构造函数参数中的 MockMessageHandler
提供 Mockito ArgumentCaptor<Message<?>>
。MockMessageHandler
的每个请求消息都会被该 ArgumentCaptor
捕获。在测试期间,你可以使用它的 getValue()
和 getAllValues()
方法验证和断言这些请求消息。
In addition, you can supply a Mockito ArgumentCaptor<Message<?>>
to the MockMessageHandler
in a constructor argument.
Each request message for the MockMessageHandler
is captured by that ArgumentCaptor
.
During the test, you can use its getValue()
and getAllValues()
methods to verify and assert those request messages.
MockIntegrationContext
提供了一个 substituteMessageHandlerFor()
API,使用此 API 可以用 MockMessageHandler
替换正在测试的端点中的实际已配置 MessageHandler
。
The MockIntegrationContext
provides a substituteMessageHandlerFor()
API that lets you replace the actual configured MessageHandler
with a MockMessageHandler
in the endpoint under test.
以下示例展示了一个典型的使用场景:
The following example shows a typical usage scenario:
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());
即使对于具有 queue 目标的通道适配器,也必须使用正则 mocking(或 dynamic)。 |
The regular |
有关更多信息,请参阅 MockIntegration
和 MockMessageHandler
Javadoc。
See the MockIntegration
and MockMessageHandler
Javadoc for more information.
Other Resources
除了探索框架本身中的测试用例之外, Spring Integration Samples repository 有一些专门用于展示测试的示例应用程序,例如 testing-examples
和 advanced-testing-examples
。在某些情况下,示例本身具有综合性的端到端测试,例如 file-split-ftp
示例。
As well as exploring the test cases in the framework itself, the Spring Integration Samples repository has some sample applications specifically made to show testing, such as testing-examples
and advanced-testing-examples
.
In some cases, the samples themselves have comprehensive end-to-end tests, such as the file-split-ftp
sample.