此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Integration 6.5.1spring-doc.cadn.net.cn

测试支持

Spring Integration 提供了许多实用程序和注释来帮助您测试应用程序。 测试支持由两个模块提供:spring-doc.cadn.net.cn

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

对企业测试的彻底处理超出了本参考手册的范围。 参见 Gregor Hohpe 和 Wendy Istvanick 撰写的 “企业集成项目中的测试驱动开发” 论文,了解测试目标集成解决方案的想法和原则。spring-doc.cadn.net.cn

Spring Integration 测试框架和测试实用程序完全基于现有的 JUnit、Hamcrest 和 Mockito 库。 应用程序上下文交互基于 Spring 测试框架。 有关详细信息,请参阅这些项目的文档。spring-doc.cadn.net.cn

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

这些工具和库中的大多数都用于 Spring Integration 测试。 此外,从 GitHub 存储库(在test每个模块的目录),您可以发现有关如何为集成解决方案构建自己的测试的想法。spring-doc.cadn.net.cn

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

测试实用程序

spring-integration-test-support模块提供了用于单元测试的实用程序和帮助程序。spring-doc.cadn.net.cn

测试Utilities

TestUtilsclass 主要用于 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

createTestApplicationContext()factory 方法会产生一个TestApplicationContext实例与提供的 Spring Integration 环境。spring-doc.cadn.net.cn

请参阅其他 JavadocTestUtils方法,了解有关此类的更多信息。spring-doc.cadn.net.cn

OnlyOnceTrigger

OnlyOnceTrigger当只需要生成一条测试消息并验证行为而不影响其他周期消息时,对于轮询端点非常有用。 以下示例演示如何配置OnlyOnceTrigger:spring-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 条件

@LongRunningTest存在条件注释,以指示在RUN_LONG_INTEGRATION_TESTSenvironment 或 system 属性设置为true. 否则,将跳过它。spring-doc.cadn.net.cn

Hamcrest 和 Mockito 匹配器

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)));
}

MockitoMessageMatchersfactory 可用于存根和验证的模拟,如以下示例所示: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引入用于 AssertJmatches()断言。 它需要一个Message对象作为期望。 还可以配置 ot 标头以从期望中排除以及从要断言的实际消息中排除。spring-doc.cadn.net.cn

Spring 集成和测试上下文

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

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

    @Autowired
    private MockIntegrationContext mockIntegrationContext;

}

@SpringIntegrationTest注释填充MockIntegrationContextbean,您可以将其自动连接到测试类以访问其方法。 使用noAutoStartup选项,Spring Integration Test Framework 会阻止通常autoStartup=true从开始。 端点与提供的模式匹配,这些模式支持以下简单模式样式:xxx*,xxx,*xxxxxx*yyy.spring-doc.cadn.net.cn

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

@SpringIntegrationTest尊重org.springframework.test.context.NestedTestConfiguration语义,因此它可以在外部类(甚至是它的超类)上声明 - 和@SpringIntegrationTest环境将可用于继承@Nested测试。spring-doc.cadn.net.cn

MockIntegrationContext旨在用于目标测试用例,以便在实际应用程序上下文中对 Bean 进行修改。 例如,具有autoStartupoverrideden 为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);
}
mySourceEndpoint这里指的是SourcePollingChannelAdapter为此,我们将实数替换为MessageSource与我们的模拟。 同样,MockIntegrationContext.substituteMessageHandlerFor()需要一个 bean 名称IntegrationConsumer,它包装了一个MessageHandler作为端点。

执行测试后,您可以使用以下命令将端点 Bean 的状态恢复到实际配置MockIntegrationContext.resetBeans():spring-doc.cadn.net.cn

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

从 6.3 版开始,MockIntegrationContext.substituteTriggerFor()引入了 API。 这可以用来代替真实的TriggerAbstractPollingEndpoint. 例如,生产配置可能依赖于每日(甚至每周)的 cron 计划。 任何自定义Trigger可以注入目标端点以缓解时间跨度。 例如,上面提到的OnlyOnceTrigger建议立即安排轮询任务并仅执行一次的行为。spring-doc.cadn.net.cn

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

集成模拟

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

模拟集成

MockIntegrationfactory 提供了一个 API 来为 Spring Integration Bean 构建模拟,这些 Bean 是集成流的一部分(MessageSource,MessageProducer,MessageHandlerMessageChannel). 您可以在配置阶段以及目标测试方法中使用目标模拟来替换实际端点,然后再执行验证和断言,如以下示例所示: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 不同MessageSourcemock 对象,则MockMessageHandler是常客AbstractMessageProducingHandler扩展,以链 API 来处理传入消息的存根。 这MockMessageHandler提供handleNext(Consumer<Message<?>>)为下一个请求消息指定单向存根。 它用于模拟不产生回复的消息处理程序。 这handleNextAndReply(Function<Message<?>, ?>)用于对下一个请求消息执行相同的存根逻辑并为其生成回复。 可以将它们链接起来,以模拟所有预期请求消息变体的任何任意请求-回复方案。 这些使用者和函数将应用于传入消息,从堆栈中一次一个,直到最后一条,然后用于所有剩余消息。 行为类似于 MockitoAnswerdoReturn()应用程序接口。spring-doc.cadn.net.cn

此外,您还可以提供 MockitoArgumentCaptor<Message<?>>MockMessageHandler在构造函数参数中。 每个请求消息MockMessageHandler被它捕获ArgumentCaptor. 在测试过程中,您可以使用其getValue()getAllValues()验证和断言这些请求消息的方法。spring-doc.cadn.net.cn

MockIntegrationContext提供一个substituteMessageHandlerFor()API 可让您替换实际配置的MessageHandler使用MockMessageHandler在被测端点中。spring-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());
常规MessageHandlermocking(或MockMessageHandler) 甚至必须用于ReactiveStreamsConsumer使用ReactiveMessageHandler配置。

请参阅MockIntegrationMockMessageHandlerJavadoc 了解更多信息。spring-doc.cadn.net.cn

其他资源

除了探索框架本身中的测试用例外,Spring Integration Samples 存储库还有一些专门用于显示测试的示例应用程序,例如testing-examplesadvanced-testing-examples. 在某些情况下,样本本身具有全面的端到端测试,例如file-split-ftp样本。spring-doc.cadn.net.cn