此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 spring-cloud-contract 4.1.5! |
消息
Spring Cloud Contract 允许您验证使用消息传递作为 通讯方式。本文档中展示的所有集成都可以与 Spring 一起使用。 但你也可以创建自己的一个并使用它。
消息传递 DSL 顶级元素
用于消息传递的 DSL 看起来与专注于 HTTP 的 DSL 略有不同。这 以下部分解释了差异:
由方法触发的输出
输出消息可以通过调用方法(例如,当合约是
started 和发送消息时),如以下示例所示:Scheduler
def dsl = Contract.make {
// Human readable description
description 'Some description'
// Label by means of which the output message can be triggered
label 'some_label'
// input to the contract
input {
// the contract will be triggered by a method
triggeredBy('bookReturnedTriggered()')
}
// output message of the contract
outputMessage {
// destination to which the output message will be sent
sentTo('output')
// the body of the output message
body('''{ "bookName" : "foo" }''')
// the headers of the output message
headers {
header('BOOK-NAME', 'foo')
}
}
}
# Human readable description
description: Some description
# Label by means of which the output message can be triggered
label: some_label
input:
# the contract will be triggered by a method
triggeredBy: bookReturnedTriggered()
# output message of the contract
outputMessage:
# destination to which the output message will be sent
sentTo: output
# the body of the output message
body:
bookName: foo
# the headers of the output message
headers:
BOOK-NAME: foo
在前面的示例案例中,如果调用了调用的方法,则会发送输出消息。在消息发布者方面,我们生成一个
test 调用该方法来触发消息。在使用者端,您可以使用 来触发消息。output
bookReturnedTriggered
some_label
集成
您可以使用以下集成配置之一:
-
阿帕奇骆驼
-
Spring 集成
-
Spring Cloud 流
-
Spring JMS
由于我们使用 Spring Boot,如果您已将这些库之一添加到 Classpath 中,则所有 消息收发配置是自动设置的。
记得把你的
生成的测试。否则,Spring Cloud Contract 的消息传递部分不会
工作。@AutoConfigureMessageVerifier |
如果要使用 Spring Cloud Stream,请记得在 上添加测试依赖,如下所示: Maven 系列
Gradle
|
手动集成测试
测试使用的主要接口是 和 。
它定义了如何发送和接收消息。org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender
org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver
在测试中,您可以注入 a 来发送和接收
遵循 Contract 的消息。然后添加到您的测试中。
以下示例显示了如何执行此操作:ContractVerifierMessageExchange
@AutoConfigureMessageVerifier
@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureMessageVerifier
public static class MessagingContractTests {
@Autowired
private MessageVerifier verifier;
...
}
如果您的测试也需要存根,则包括
消息传送配置,因此您只需要一个注释。@AutoConfigureStubRunner |
生产者端消息收发测试生成
在 DSL 中包含 or 部分会导致创建测试
在出版商方面。默认情况下,将创建 JUnit 4 测试。但是,还有一个
可以创建 JUnit 5、TestNG 或 Spock 测试。input
outputMessage
传递给的目标或可以具有不同的
不同消息传递实现的含义。对于 Stream 和 Integration,它是
首先解析为 A of a 通道。那么,如果没有这样的 ,
它被解析为通道名称。对于 Camel,这是一个特定的组件(例如,)。messageFrom sentTo destination destination jms |
考虑以下合约:
def contractDsl = Contract.make {
name "foo"
label 'some_label'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('activemq:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
messagingContentType(applicationJson())
}
}
}
label: some_label
input:
triggeredBy: bookReturnedTriggered
outputMessage:
sentTo: activemq:output
body:
bookName: foo
headers:
BOOK-NAME: foo
contentType: application/json
对于前面的示例,将创建以下测试:
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.Test;
import org.junit.Rule;
import javax.inject.Inject;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging;
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*;
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil.headers;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes;
public class FooTest {
@Inject ContractVerifierMessaging contractVerifierMessaging;
@Inject ContractVerifierObjectMapper contractVerifierObjectMapper;
@Test
public void validate_foo() throws Exception {
// when:
bookReturnedTriggered();
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("activemq:output",
contract(this, "foo.yml"));
assertThat(response).isNotNull();
// and:
assertThat(response.getHeader("BOOK-NAME")).isNotNull();
assertThat(response.getHeader("BOOK-NAME").toString()).isEqualTo("foo");
assertThat(response.getHeader("contentType")).isNotNull();
assertThat(response.getHeader("contentType").toString()).isEqualTo("application/json");
// and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()));
assertThatJson(parsedJson).field("['bookName']").isEqualTo("foo");
}
}
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import spock.lang.Specification
import javax.inject.Inject
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson
import static org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil.headers
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes
class FooSpec extends Specification {
@Inject ContractVerifierMessaging contractVerifierMessaging
@Inject ContractVerifierObjectMapper contractVerifierObjectMapper
def validate_foo() throws Exception {
when:
bookReturnedTriggered()
then:
ContractVerifierMessage response = contractVerifierMessaging.receive("activemq:output",
contract(this, "foo.yml"))
response != null
and:
response.getHeader("BOOK-NAME") != null
response.getHeader("BOOK-NAME").toString() == 'foo'
response.getHeader("contentType") != null
response.getHeader("contentType").toString() == 'application/json'
and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()))
assertThatJson(parsedJson).field("['bookName']").isEqualTo("foo")
}
}
Consumer Stub 生成
与 HTTP 部分不同,在消息传递中,我们需要在 JAR 中发布合约定义,并且 一个存根。然后在使用者端对其进行解析,并创建适当的存根路由。
如果您在 Classpath 上有多个框架,则 Stub Runner 需要
定义应该使用哪一个。假设您拥有 AMQP、Spring Cloud Stream 和 Spring Integration
在 Classpath 上,并且您希望使用 Spring AMQP。然后,您需要设置 和 。
这样,唯一剩下的框架就是 Spring AMQP。stubrunner.stream.enabled=false stubrunner.integration.enabled=false |
存根触发
要触发消息,请使用该接口,如下例所示:StubTrigger
import java.util.Collection;
import java.util.Map;
/**
* Contract for triggering stub messages.
*
* @author Marcin Grzejszczak
*/
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.
* @param ivyNotation ivy notation of a stub
* @param labelName name of the label to trigger
* @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.
* @param labelName name of the label to trigger
* @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();
/**
* Feature related to messaging.
* @return a mapping of ivy notation of a dependency to all the labels it has.
*/
Map<String, Collection<String>> labels();
}
为方便起见,接口扩展了 ,因此您只需要一个
或测试中的 other。StubFinder
StubTrigger
StubTrigger
提供了以下触发消息的选项:
使用 Apache Camel 进行消费者端消息传递
Spring Cloud Contract Stub Runner 的消息传递模块为您提供了一种与 Apache Camel 集成的简便方法。 对于提供的工件,它会自动下载存根并注册所需的 路线。
将 Apache Camel 添加到项目中
您可以在 Classpath 上同时拥有 Apache Camel 和 Spring Cloud Contract Stub Runner。
请记住使用 .@AutoConfigureStubRunner
例子
假设我们有以下 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
│ └── bookReturned1.groovy
└── mappings
现在考虑以下合约:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('rabbitmq:output?queue=output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
要从标签触发消息,我们使用接口,如下所示:return_book_1
StubTrigger
stubFinder.trigger("return_book_1")
这将向 Contract 的输出消息中描述的目标发送消息。
使用 Spring 集成的消费者端消息传递
Spring Cloud Contract Stub Runner 的消息传递模块为您提供了一种简单的方法 与 Spring 集成。对于提供的工件,它会自动下载 存根并注册所需的路由。
将 Runner 添加到项目中
您可以在 Spring Integration 和 Spring Cloud Contract Stub Runner 上同时拥有
classpath 的请记住使用 .@AutoConfigureStubRunner
例子
假设您有以下 Maven 存储库,其中包含为应用程序部署的存根:integrationService
└── .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
│ └── bookReturned1.groovy
└── mappings
考虑以下合约:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
现在考虑以下 Spring 集成路由:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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>
要从标签触发消息,请使用接口,如
遵循:return_book_1
StubTrigger
stubFinder.trigger('return_book_1')
这将向 Contract 的输出消息中描述的目标发送消息。
使用 Spring Cloud Stream 进行消费者端消息传递
Spring Cloud Contract Stub Runner 的消息传递模块为您提供了一种简单的方法 与 Spring Stream 集成。对于提供的工件,它会自动下载 stubs 并注册所需的路由。
如果 Stub Runner 与 Stream 或字符串的集成
首先解析为 channel 的 ,并且不存在
destination 解析为通道名称。messageFrom sentTo destination destination |
如果要使用 Spring Cloud Stream,请记得添加对 test support 的依赖,如下所示: Maven 系列
Gradle
|
将 Runner 添加到项目中
您可以在 Spring Cloud Stream 和 Spring Cloud Contract Stub Runner 上同时拥有
classpath 的请记住使用 .@AutoConfigureStubRunner
例子
假设您有以下 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
│ └── bookReturned1.groovy
└── mappings
考虑以下合约:
Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
现在考虑以下 Spring Cloud Stream 函数配置:
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
@Configuration(proxyBeanMethods = true)
@EnableAutoConfiguration
protected static class Config {
@Bean
Function<String, String> test1() {
return (input) -> {
println "Test 1 [${input}]"
return input
}
}
}
现在考虑以下 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:
test1-in-0:
destination: returnBook
test1-out-0:
destination: outputToAssertBook
function:
definition: test1
server:
port: 0
debug: true
要从标签触发消息,请使用
遵循:return_book_1
StubTrigger
stubFinder.trigger('return_book_1')
这将向 Contract 的输出消息中描述的目标发送消息。
使用 Spring JMS 的消费者端消息传递
Spring Cloud Contract Stub Runner 的消息传递模块提供了一种简单的方法 与 Spring JMS 集成。
该集成假定您有一个正在运行的 JMS 代理实例。
将 Runner 添加到项目中
您需要在 Classpath 上同时具有 Spring JMS 和 Spring Cloud Contract Stub Runner。请记住为您的测试类添加注释
跟。@AutoConfigureStubRunner
例子
假设存根结构如下所示:
├── stubs
└── bookReturned1.groovy
进一步假设以下测试配置:
stubrunner:
repository-root: stubs:classpath:/stubs/
ids: my:stubs
stubs-mode: remote
spring:
activemq:
send-timeout: 1000
jms:
template:
receive-timeout: 1000
现在考虑以下合约:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOKNAME', 'foo')
}
}
}
要从标签触发消息,我们使用接口,如下所示:return_book_1
StubTrigger
stubFinder.trigger('return_book_1')
这将向 Contract 的输出消息中描述的目标发送消息。