如需使用最新稳定版本,请使用 Spring Integration 7.0.4spring-doc.cadn.net.cn

测试支持

Spring Integration 提供了一系列工具和注解,以帮助您测试应用程序。 测试支持由两个模块提供:spring-doc.cadn.net.cn

spring-integration-test-support(在 5.0 之前的版本中为spring-integration-test)提供了用于单元测试的基本、独立工具、规则和匹配器。 (它也不依赖于 Spring Integration 本身,并在 Framework 测试内部使用)。 spring-integration-test 旨在帮助进行集成测试,并提供一个全面的高级 API 来模拟集成组件并验证各个组件的行为,包括整个集成流程或其中的一部分。spring-doc.cadn.net.cn

对企业中测试的全面论述超出了本参考手册的范围。 请参阅 Gregor Hohpe 和 Wendy Istvanick 撰写的《企业集成项目中的测试驱动开发》("Test-Driven Development in Enterprise Integration Projects")一文,以获取针对目标集成解决方案进行测试的灵感与原则来源。spring-doc.cadn.net.cn

Spring 集成测试框架和测试工具完全基于现有的 JUnit、Hamcrest 和 Mockito 库。 应用上下文交互基于 Spring 测试框架。 有关这些项目的更多信息,请参阅其文档。spring-doc.cadn.net.cn

得益于 EIP 在 Spring Integration Framework 中的规范实现及其一等公民(如 MessageChannelEndpointMessageHandler)、抽象层和松耦合原则,您可以实现任何复杂度的集成解决方案。 借助用于流程定义的 Spring Integration API,您可以改进、修改甚至替换流程的某些部分,而不会(大多数情况下)影响集成解决方案中的其他组件。 测试此类集成解决方案仍然是一个挑战,无论是从端到端的角度还是从独立隔离的角度来看。 现有的几种工具可以帮助测试或模拟某些集成协议,并且它们与 Spring Integration 通道适配器配合良好。 此类工具的示例如下:spring-doc.cadn.net.cn

这些工具和库大多用于 Spring 集成测试。 此外,您还可以从 GitHub 仓库(每个模块的test目录中)发现如何构建您自己的集成解决方案测试的思路。spring-doc.cadn.net.cn

本章其余部分介绍了 Spring Integration 提供的测试工具和实用程序。spring-doc.cadn.net.cn

测试工具

The spring-integration-test-support 模块为单元测试提供了实用工具和辅助函数。spring-doc.cadn.net.cn

测试工具

The TestUtils 类主要用于 JUnit 测试中的属性断言,如下例所示:spring-doc.cadn.net.cn

@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,并提供了从目标私有属性获取值的能力。 如前例所示,它还支持通过点符号访问嵌套属性。spring-doc.cadn.net.cn

The createTestApplicationContext() 工厂方法使用提供的 Spring Integration 环境生成一个 TestApplicationContext 实例。spring-doc.cadn.net.cn

有关此类的更多信息,请参阅其他 TestUtils 方法的 Javadocspring-doc.cadn.net.cn

使用OnlyOnceTrigger

OnlyOnceTrigger 在需要仅生成一条测试消息并验证行为而不影响其他周期性消息时,用于轮询端点非常有用。 以下示例展示了如何配置 OnlyOnceTriggerspring-doc.cadn.net.cn

<bean id="testTrigger" class="org.springframework.integration.test.util.OnlyOnceTrigger" />

<int:poller id="jpaPoller" trigger="testTrigger">
    <int:transactional transaction-manager="transactionManager" />
</int:poller>

下面的示例展示了如何使用前面的 OnlyOnceTrigger 配置进行测试:spring-doc.cadn.net.cn

@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 LongRunningIntegrationTest JUnit 4 测试规则用于指示当 RUN_LONG_INTEGRATION_TESTS 环境或系统属性设置为 true 时是否应运行测试。 否则,该测试将被跳过。 出于相同原因,自 5.1 版本起,为 JUnit 5 测试提供了 @LongRunningTest 条件注解。spring-doc.cadn.net.cn

Hamcrest 和 Mockito 匹配器

The org.springframework.integration.test.matcher 包包含多个 Matcher 实现,用于在单元测试中断言 Message 及其属性。 以下示例展示了如何使用其中一个匹配器(PayloadMatcher):spring-doc.cadn.net.cn

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工厂可用于存根和验证的模拟,如下例所示:spring-doc.cadn.net.cn

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 条件和谓词

从版本 5.2 开始,MessagePredicate 被引入用于 AssertJ matches() 断言。 它需要一个 Message 对象作为期望值。 此外,还可以配置头信息以从期望值和实际断言消息中排除这些头信息。spring-doc.cadn.net.cn

Spring 集成与测试上下文

通常,Spring 应用程序的测试使用 Spring Test Framework。 由于 Spring Integration 基于 Spring Framework 基础架构,因此我们可以使用 Spring Test Framework 执行的所有操作也适用于集成流的测试。 org.springframework.integration.test.context 包提供了一些组件,用于增强集成需求的测试上下文。 首先,我们使用 @SpringIntegrationTest 注解配置我们的测试类以启用 Spring Integration Test Framework,如下例所示:spring-doc.cadn.net.cn

@SpringJUnitConfig
@SpringIntegrationTest(noAutoStartup = {"inboundChannelAdapter", "*Source*"})
public class MyIntegrationTests {

    @Autowired
    private MockIntegrationContext mockIntegrationContext;

}

@SpringIntegrationTest 注解会填充一个 MockIntegrationContext Bean,您可以将其自动注入到测试类中以访问其方法。 使用 noAutoStartup 选项时,Spring 集成测试框架会阻止通常处于 autoStartup=true 状态的端点启动。 这些端点会根据提供的模式进行匹配,该模式支持以下简单的模式样式:xxx*xxx*xxx 以及 xxx*yyyspring-doc.cadn.net.cn

当我们希望入站通道适配器(例如 AMQP 入站网关、JDBC 轮询通道适配器、客户端模式的 WebSocket 消息生产者等)不与目标系统建立真实连接时,这非常有用。spring-doc.cadn.net.cn

The @SpringIntegrationTest 继承自 org.springframework.test.context.NestedTestConfiguration 的语义,因此可以声明在外层类(甚至其父类)上——这样 @SpringIntegrationTest 环境将对继承的 @Nested 测试可用。spring-doc.cadn.net.cn

MockIntegrationContext 用于在实际应用上下文中修改 Bean 的目标测试用例中。 例如,将 autoStartup 覆盖为 false 的端点可以被替换为模拟对象,如下例所示:spring-doc.cadn.net.cn

@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 的状态恢复为实际配置:spring-doc.cadn.net.cn

@After
public void tearDown() {
    this.mockIntegrationContext.resetBeans();
}

从版本 6.3 开始,引入了 MockIntegrationContext.substituteTriggerFor() API。 这可用于替换 AbstractPollingEndpoint 中的真实 Trigger。 例如,生产环境的配置可能依赖于每日(甚至每周)的 cron 调度任务。 任何自定义的 Trigger 都可以注入到目标端点,以缩短时间跨度。 例如,上述提到的 OnlyOnceTrigger 建议一种行为:立即调度轮询任务,且仅执行一次。spring-doc.cadn.net.cn

有关更多信息,请参阅javadocspring-doc.cadn.net.cn

集成模拟

org.springframework.integration.test.mock 包提供了用于模拟、存根和验证 Spring Integration 组件活动的工具和实用程序。 模拟功能完全基于并兼容广为人知的 Mockito 框架。 (当前的 Mockito 传递依赖版本为 2.5.x 或更高版本。)spring-doc.cadn.net.cn

模拟集成

The MockIntegration factory 提供了用于构建 Spring Integration 集成流中组成部分(MessageSourceMessageProducerMessageHandlerMessageChannel)的 Mock 对象的 API。 您可以在配置阶段以及目标测试方法中使用目标 Mock 对象,在执行验证和断言之前替换真实的端点,如下例所示:spring-doc.cadn.net.cn

<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 配置来实现与前面示例相同的配置:spring-doc.cadn.net.cn

@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,如下例所示:spring-doc.cadn.net.cn

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 的 AnswerdoReturn() API。spring-doc.cadn.net.cn

此外,您还可以在构造参数中将 Mockito ArgumentCaptor<Message<?>> 提供给 MockMessageHandler。 每个针对 MockMessageHandler 的请求消息都会被该 ArgumentCaptor 捕获。 在测试期间,您可以使用其 getValue()getAllValues() 方法来验证和断言这些请求消息。spring-doc.cadn.net.cn

The MockIntegrationContext 提供了一个 substituteMessageHandlerFor() API,允许您在被测端点中将实际配置的 MessageHandler 替换为 MockMessageHandlerspring-doc.cadn.net.cn

以下示例展示了一个典型的使用场景:spring-doc.cadn.net.cn

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)。

查看 MockIntegrationMockMessageHandler 的 Javadoc 以获取更多信息。spring-doc.cadn.net.cn

其他资源

除了探索框架本身的测试用例外,Spring Integration 示例仓库还提供了一些专门用于展示测试的示例应用程序,例如 testing-examplesadvanced-testing-examples。 在某些情况下,示例本身包含全面的端到端测试,例如 file-split-ftp 示例。spring-doc.cadn.net.cn