92. 消息传送存根
Stub Runner 可以在内存中运行已发布的存根。它可以与以下框架集成:
-
Spring Integration
-
Spring 云流
-
Apache Camel
-
Spring AMQP
它还提供了与市场上任何其他解决方案集成的切入点。
Tip
如果在 Classpath 上有多个框架,则 Stub Runner 将需要定义应使用的框架。假设您在 Classpath 上同时拥有 AMQP,Spring Cloud Stream 和 Spring Integration。然后,您需要设置stubrunner.stream.enabled=false
和stubrunner.integration.enabled=false
。这样,剩下的唯一框架就是 Spring AMQP。
92.1 存根触发
要触发消息,请使用StubTrigger
界面:
package org.springframework.cloud.contract.stubrunner;
import java.util.Collection;
import java.util.Map;
public interface StubTrigger {
/**
* Triggers an event by a given label for a given {@code groupid:artifactid} notation.
* You can use only {@code artifactId} too.
*
* Feature related to messaging.
* @return true - if managed to run a trigger
*/
boolean trigger(String ivyNotation, String labelName);
/**
* Triggers an event by a given label.
*
* Feature related to messaging.
* @return true - if managed to run a trigger
*/
boolean trigger(String labelName);
/**
* Triggers all possible events.
*
* Feature related to messaging.
* @return true - if managed to run a trigger
*/
boolean trigger();
/**
* Returns a mapping of ivy notation of a dependency to all the labels it has.
*
* Feature related to messaging.
*/
Map<String, Collection<String>> labels();
}
为了方便起见,StubFinder
界面扩展了StubTrigger
,因此您在测试中只需要一个即可。
StubTrigger
为您提供以下选项来触发消息:
92.1.1 按标签触发
stubFinder.trigger('return_book_1')
92.1.2 按组和工件 ID 触发
stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:streamService', 'return_book_1')
92.1.3 由工件 ID 触发
stubFinder.trigger('streamService', 'return_book_1')
92.1.4 触发所有消息
stubFinder.trigger()
92.2 存根转轮骆驼
Spring Cloud Contract Verifier Stub Runner 的消息传递模块为您提供了一种与 Apache Camel 集成的简便方法。对于提供的工件,它将自动下载存根并注册所需的路由。
92.2.1 将其添加到项目中
在 Classpath 上同时安装 Apache Camel 和 Spring Cloud Contract Stub Runner 就足够了。请记住用@AutoConfigureStubRunner
Comments 测试类。
92.2.2 禁用功能
如果您需要禁用此功能,只需传递stubrunner.camel.enabled=false
属性即可。
92.2.3 Examples
Stubs structure
让我们假设我们具有以下 Maven 存储库,其中已为camelService
应用程序部署了存根。
└── .m2
└── repository
└── io
└── codearte
└── accurest
└── stubs
└── camelService
├── 0.0.1-SNAPSHOT
│ ├── camelService-0.0.1-SNAPSHOT.pom
│ ├── camelService-0.0.1-SNAPSHOT-stubs.jar
│ └── maven-metadata-local.xml
└── maven-metadata-local.xml
存根包含以下结构:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ ├── bookDeleted.groovy
│ ├── bookReturned1.groovy
│ └── bookReturned2.groovy
└── mappings
让我们考虑以下 Contract(用 1 编号):
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('jms:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
和数字 2
Contract.make {
label 'return_book_2'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
情况 1(无 Importing 消息)
为了通过return_book_1
标签触发消息,我们将使用StubTigger
接口,如下所示
stubFinder.trigger('return_book_1')
接下来,我们要收听发送到jms:output
的消息的输出
Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000)
并且收到的消息将通过以下 assert
receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
场景 2(由 Importing 触发输出)
由于已为您设置了 Route,仅向jms:output
目标发送一条消息就足够了。
producerTemplate.sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
接下来,我们要收听发送到jms:output
的消息的输出
Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000)
并且收到的消息将通过以下 assert
receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
情况 3(Importing 无输出)
由于已为您设置了 Route,仅向jms:output
目标发送一条消息就足够了。
producerTemplate.sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header'])
92.3 Stub Runner 集成
Spring Cloud Contract Verifier Stub Runner 的消息传递模块为您提供了一种与 Spring Integration 集成的简便方法。对于提供的工件,它会自动下载存根并注册所需的路由。
92.3.1 将运行器添加到项目
您可以在 Classpath 上同时使用 Spring Integration 和 Spring Cloud Contract Stub Runner。请记住用@AutoConfigureStubRunner
Comments 测试类。
92.3.2 禁用功能
如果需要禁用此功能,请设置stubrunner.integration.enabled=false
属性。
假定您具有以下integrationService
应用程序已部署存根的 Maven 存储库:
└── .m2
└── repository
└── io
└── codearte
└── accurest
└── stubs
└── integrationService
├── 0.0.1-SNAPSHOT
│ ├── integrationService-0.0.1-SNAPSHOT.pom
│ ├── integrationService-0.0.1-SNAPSHOT-stubs.jar
│ └── maven-metadata-local.xml
└── maven-metadata-local.xml
进一步假设存根包含以下结构:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ ├── bookDeleted.groovy
│ ├── bookReturned1.groovy
│ └── bookReturned2.groovy
└── mappings
考虑以下 Contract(编号 1 ):
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
现在考虑 2 :
Contract.make {
label 'return_book_2'
input {
messageFrom('input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
以及以下 Spring Integration Route:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- REQUIRED FOR TESTING -->
<bridge input-channel="output"
output-channel="outputTest"/>
<channel id="outputTest">
<queue/>
</channel>
</beans:beans>
这些示例使自己适合三种情况:
情况 1(无 Importing 消息)
要通过return_book_1
标签触发消息,请使用StubTigger
界面,如下所示:
stubFinder.trigger('return_book_1')
收听发送到output
的消息的输出:
Message<?> receivedMessage = messaging.receive('outputTest')
收到的消息将通过以下 assert:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 2(由 Importing 触发输出)
由于已为您设置了 Route,因此您可以向output
目标发送消息:
messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')
收听发送到output
的消息的输出:
Message<?> receivedMessage = messaging.receive('outputTest')
收到的消息传递以下 assert:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
情况 3(Importing 无输出)
由于已为您设置了 Route,因此您可以向input
目标发送消息:
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
92.4 存根转轮流
Spring Cloud Contract Verifier Stub Runner 的消息传递模块为您提供了一种与 Spring Stream 集成的简便方法。对于提供的工件,它会自动下载存根并注册所需的路由。
Warning
如果 Stub Runner 与 Stream 集成,则首先将messageFrom
或sentTo
字符串解析为通道的destination
,并且不存在这样的destination
,则将目的地解析为通道名。
Tip
如果您想使用 Spring Cloud Stream,请记住添加对org.springframework.cloud:spring-cloud-stream-test-support
的依赖。
Maven.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
Gradle.
testCompile "org.springframework.cloud:spring-cloud-stream-test-support"
92.4.1 将运行器添加到项目
您可以在 Classpath 上同时使用 Spring Cloud Stream 和 Spring Cloud Contract Stub Runner。请记住用@AutoConfigureStubRunner
Comments 测试类。
92.4.2 禁用功能
如果需要禁用此功能,请设置stubrunner.stream.enabled=false
属性。
假设您具有以下 Maven 存储库,其中已为streamService
应用程序部署了存根:
└── .m2
└── repository
└── io
└── codearte
└── accurest
└── stubs
└── streamService
├── 0.0.1-SNAPSHOT
│ ├── streamService-0.0.1-SNAPSHOT.pom
│ ├── streamService-0.0.1-SNAPSHOT-stubs.jar
│ └── maven-metadata-local.xml
└── maven-metadata-local.xml
进一步假设存根包含以下结构:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ ├── bookDeleted.groovy
│ ├── bookReturned1.groovy
│ └── bookReturned2.groovy
└── mappings
考虑以下 Contract(编号 1 ):
Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
现在考虑 2 :
Contract.make {
label 'return_book_2'
input {
messageFrom('bookStorage')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
}
outputMessage {
sentTo('returnBook')
body([
bookName: 'foo'
])
headers { header('BOOK-NAME', 'foo') }
}
}
现在考虑以下 Spring 配置:
stubrunner.repositoryRoot: classpath:m2repo/repository/
stubrunner.ids: org.springframework.cloud.contract.verifier.stubs:streamService:0.0.1-SNAPSHOT:stubs
stubrunner.stubs-mode: remote
spring:
cloud:
stream:
bindings:
output:
destination: returnBook
input:
destination: bookStorage
server:
port: 0
debug: true
这些示例使自己适合三种情况:
情况 1(无 Importing 消息)
要通过return_book_1
标签触发消息,请使用StubTrigger
界面,如下所示:
stubFinder.trigger('return_book_1')
要收听发送到destination
为returnBook
的 Channels 的消息的输出,请执行以下操作:
Message<?> receivedMessage = messaging.receive('returnBook')
收到的消息传递以下 assert:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 2(由 Importing 触发输出)
由于已为您设置了 Route,因此您可以向bookStorage
destination
发送消息:
messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
收听发送到returnBook
的消息的输出:
Message<?> receivedMessage = messaging.receive('returnBook')
收到的消息传递以下 assert:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
情况 3(Importing 无输出)
由于已为您设置了 Route,因此您可以向output
目标发送消息:
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
92.5 Stub Runner Spring AMQP
Spring Cloud Contract Verifier Stub Runner 的消息传递模块提供了一种与 Spring AMQP 的 Rabbit 模板集成的简便方法。对于提供的工件,它会自动下载存根并注册所需的路由。
集成尝试独立工作(即,不与正在运行的 RabbitMQ 消息代理进行交互)。它在应用程序上下文中期望RabbitTemplate
并将其用作名为@SpyBean
的 Spring Boot 测试。结果,它可以使用模仿 Spy 功能来验证和检查应用程序发送的消息。
在消息使用者方面,存根运行器考虑应用程序上下文中所有带有@RabbitListener
Comments 的端点和所有SimpleMessageListenerContainer
对象。
由于通常将消息发送到 AMQP 中的 Transaction 所,因此消息 Contract 包含 Transaction 所名称作为目的地。另一侧的消息侦听器绑定到队列。绑定将交换连接到队列。如果触发了消息 Contract,则 Spring AMQP 存根运行器集成会在应用程序上下文中查找与该交换匹配的绑定。然后,它从 Spring 交换机收集队列,并尝试查找绑定到这些队列的消息侦听器。将为所有匹配的消息侦听器触发该消息。
如果您需要使用路由键,则足以通过amqp_receivedRoutingKey
消息头传递它们。
92.5.1 将运行器添加到项目
您可以在 Classpath 上同时使用 Spring AMQP 和 Spring Cloud Contract Stub Runner 并设置属性stubrunner.amqp.enabled=true
。请记住用@AutoConfigureStubRunner
Comments 测试类。
Tip
如果您已经在 Classpath 上具有 Stream and Integration,则需要通过设置stubrunner.stream.enabled=false
和stubrunner.integration.enabled=false
属性来显式禁用它们。
假设您具有以下 Maven 存储库,其中已为spring-cloud-contract-amqp-test
应用程序部署了存根。
└── .m2
└── repository
└── com
└── example
└── spring-cloud-contract-amqp-test
├── 0.4.0-SNAPSHOT
│ ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom
│ ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar
│ └── maven-metadata-local.xml
└── maven-metadata-local.xml
进一步假设存根包含以下结构:
├── META-INF
│ └── MANIFEST.MF
└── contracts
└── shouldProduceValidPersonData.groovy
考虑以下 Contract:
Contract.make {
// Human readable description
description 'Should produce valid person data'
// Label by means of which the output message can be triggered
label 'contract-test.person.created.event'
// input to the contract
input {
// the contract will be triggered by a method
triggeredBy('createPerson()')
}
// output message of the contract
outputMessage {
// destination to which the output message will be sent
sentTo 'contract-test.exchange'
headers {
header('contentType': 'application/json')
header('__TypeId__': 'org.springframework.cloud.contract.stubrunner.messaging.amqp.Person')
}
// the body of the output message
body ([
id: $(consumer(9), producer(regex("[0-9]+"))),
name: "me"
])
}
}
现在考虑以下 Spring 配置:
stubrunner:
repositoryRoot: classpath:m2repo/repository/
ids: org.springframework.cloud.contract.verifier.stubs.amqp:spring-cloud-contract-amqp-test:0.4.0-SNAPSHOT:stubs
stubs-mode: remote
amqp:
enabled: true
server:
port: 0
触发讯息
要使用上述 Contract 触发消息,请使用StubTrigger
界面,如下所示:
stubTrigger.trigger("contract-test.person.created.event")
该消息的目的地为contract-test.exchange
,因此 Spring AMQP 存根运行器集成会查找与此交换有关的绑定。
@Bean
public Binding binding() {
return BindingBuilder.bind(new Queue("test.queue"))
.to(new DirectExchange("contract-test.exchange")).with("#");
}
绑定定义绑定队列test.queue
。结果,以下侦听器定义将与协定消息匹配并调用。
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(
ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("test.queue");
container.setMessageListener(listenerAdapter);
return container;
}
此外,以下带 Comments 的侦听器将匹配并被调用:
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "test.queue"), exchange = @Exchange(value = "contract-test.exchange", ignoreDeclarationExceptions = "true")))
public void handlePerson(Person person) {
this.person = person;
}
Note
该消息将直接移交给与匹配SimpleMessageListenerContainer
关联的MessageListener
的onMessage
方法。
Spring AMQP 测试配置
为了避免 Spring AMQP 在我们的测试期间尝试连接到正在运行的代理,请配置模拟ConnectionFactory
。
要禁用模拟的 ConnectionFactory,请设置以下属性:stubrunner.amqp.mockConnection=false
stubrunner:
amqp:
mockConnection: false