此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 spring-cloud-contract 4.2.0! |
消息
Spring Cloud Contract 允许您验证使用消息传递作为 通讯方式。本文档中展示的所有集成都可以与 Spring 一起使用。 但你也可以创建自己的一个并使用它。
消息传递 DSL 顶级元素
用于消息传递的 DSL 看起来与专注于 HTTP 的 DSL 略有不同。这 以下部分解释了差异:
由方法触发的输出
输出消息可以通过调用方法(例如Scheduler
当合约是
started 和发送消息时),如以下示例所示:
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
在前面的示例案例中,输出消息被发送到output
如果调用了bookReturnedTriggered
被调用。在消息发布者方面,我们生成一个
test 调用该方法来触发消息。在消费者方面,您可以使用some_label
以触发消息。
集成
您可以使用以下集成配置之一:
-
阿帕奇骆驼
-
Spring 集成
-
Spring Cloud 流
-
Spring JMS
由于我们使用 Spring Boot,如果您已将这些库之一添加到 Classpath 中,则所有 消息收发配置是自动设置的。
记得把@AutoConfigureMessageVerifier 在
生成的测试。否则,Spring Cloud Contract 的消息传递部分不会
工作。 |
如果要使用 Spring Cloud Stream,请记得在 Maven 系列
Gradle
|
手动集成测试
测试使用的主界面是org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender
和org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver
.
它定义了如何发送和接收消息。
在测试中,您可以注入ContractVerifierMessageExchange
发送和接收
遵循 Contract 的消息。然后添加@AutoConfigureMessageVerifier
进行测试。
以下示例显示了如何执行此作:
@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureMessageVerifier
public static class MessagingContractTests {
@Autowired
private MessageVerifier verifier;
...
}
如果您的测试也需要存根,则@AutoConfigureStubRunner 包括
消息传送配置,因此您只需要一个注释。 |
生产者端消息收发测试生成
使用input
或outputMessage
部分会导致创建测试
在出版商方面。默认情况下,将创建 JUnit 4 测试。但是,还有一个
可以创建 JUnit 5、TestNG 或 Spock 测试。
传递给messageFrom 或sentTo 可以具有不同的
不同消息传递实现的含义。对于 Stream 和 Integration,它是
首先解析为destination 的频道。然后,如果没有这样的destination ,
它被解析为通道名称。对于 Camel 来说,这是一个特定的组件(例如,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");
}
}
Spock
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 Generation
Unlike in the HTTP part, in messaging, we need to publish the contract definition inside the JAR with
a stub. Then it is parsed on the consumer side, and proper stubbed routes are created.
If you have multiple frameworks on the classpath, Stub Runner needs to
define which one should be used. Assume that you have AMQP, Spring Cloud Stream, and Spring Integration
on the classpath and that you want to use Spring AMQP. Then you need to set
stubrunner.stream.enabled=false
and stubrunner.integration.enabled=false
.
That way, the only remaining framework is Spring AMQP.
Stub triggering
To trigger a message, use the StubTrigger
interface, as the following example shows:
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();
}
For convenience, the StubFinder
interface extends StubTrigger
, so you need only one
or the other in your tests.
StubTrigger
gives you the following options to trigger a message:
Trigger by Label
The following example shows how to trigger a message with a label:
stubFinder.trigger('return_book_1')
Trigger by Group and Artifact IDs
The following example shows how to trigger a message by group and artifact IDs:
stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:streamService', 'return_book_1')
Consumer Side Messaging With Apache Camel
Spring Cloud Contract Stub Runner’s messaging module gives you an easy way to integrate with Apache Camel.
For the provided artifacts, it automatically downloads the stubs and registers the required
routes.
Adding Apache Camel to the Project
You can have both Apache Camel and Spring Cloud Contract Stub Runner on the classpath.
Remember to annotate your test class with @AutoConfigureStubRunner
.
Disabling the Functionality
If you need to disable this functionality, set the stubrunner.camel.enabled=false
property.
Examples
Assume that we have the following Maven repository with deployed stubs for the
camelService
application:
└── .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
Further, assume that the stubs contain the following structure:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ └── bookReturned1.groovy
└── mappings
Now consider the following contract:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('rabbitmq:output?queue=output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
To trigger a message from the return_book_1
label, we use the StubTrigger
interface, as follows:
stubFinder.trigger("return_book_1")
That will send out a message to the destination described in the output message of the contract.
Consumer Side Messaging with Spring Integration
Spring Cloud Contract Stub Runner’s messaging module gives you an easy way to
integrate with Spring Integration. For the provided artifacts, it automatically downloads
the stubs and registers the required routes.
Adding the Runner to the Project
You can have both Spring Integration and Spring Cloud Contract Stub Runner on the
classpath. Remember to annotate your test class with @AutoConfigureStubRunner
.
Disabling the Functionality
If you need to disable this functionality, set the
stubrunner.integration.enabled=false
property.
Examples
Assume that you have the following Maven repository with deployed stubs for the
integrationService
application:
└── .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
Further assume the stubs contain the following structure:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ └── bookReturned1.groovy
└── mappings
Consider the following contract:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
Now consider the following Spring Integration Route:
<?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>
To trigger a message from the return_book_1
label, use the StubTrigger
interface, as
follows:
stubFinder.trigger('return_book_1')
That will send out a message to the destination described in the output message of the contract.
Consumer Side Messaging With Spring Cloud Stream
Spring Cloud Contract Stub Runner’s messaging module gives you an easy way to
integrate with Spring Stream. For the provided artifacts, it automatically downloads the
stubs and registers the required routes.
If Stub Runner’s integration with the Stream messageFrom
or sentTo
strings
are resolved first as the destination
of a channel and no such destination
exists, the
destination is resolved as a channel name.
If you want to use Spring Cloud Stream, remember to add a dependency on
org.springframework.cloud:spring-cloud-stream
test support, as follows:
Maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-binder</artifactId>
<scope>test</scope>
</dependency>
Gradle
testImplementation('org.springframework.cloud:spring-cloud-stream-test-binder')
Adding the Runner to the Project
You can have both Spring Cloud Stream and Spring Cloud Contract Stub Runner on the
classpath. Remember to annotate your test class with @AutoConfigureStubRunner
.
Disabling the Functionality
If you need to disable this functionality, set the stubrunner.stream.enabled=false
property.
Examples
Assume that you have the following Maven repository with deployed stubs for the
streamService
application:
└── .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
Further assume the stubs contain the following structure:
├── META-INF
│ └── MANIFEST.MF
└── repository
├── accurest
│ └── bookReturned1.groovy
└── mappings
Consider the following contract:
Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
Now consider the following Spring Cloud Stream function configuration:
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
@Configuration(proxyBeanMethods = true)
@EnableAutoConfiguration
protected static class Config {
@Bean
Function<String, String> test1() {
return (input) -> {
println "Test 1 [${input}]"
return input
}
}
}
Now consider the following Spring configuration:
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
To trigger a message from the return_book_1
label, use the StubTrigger
interface as
follows:
stubFinder.trigger('return_book_1')
That will send out a message to the destination described in the output message of the contract.
Consumer Side Messaging With Spring JMS
Spring Cloud Contract Stub Runner’s messaging module provides an easy way to
integrate with Spring JMS.
The integration assumes that you have a running instance of a JMS broker.
Adding the Runner to the Project
You need to have both Spring JMS and Spring Cloud Contract Stub Runner on the classpath. Remember to annotate your test class
with @AutoConfigureStubRunner
.
Examples
Assume that the stub structure looks as follows:
├── stubs
└── bookReturned1.groovy
Further assume the following test configuration:
stubrunner:
repository-root: stubs:classpath:/stubs/
ids: my:stubs
stubs-mode: remote
spring:
activemq:
send-timeout: 1000
jms:
template:
receive-timeout: 1000
Now consider the following contract:
Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOKNAME', 'foo')
}
}
}
To trigger a message from the return_book_1
label, we use the StubTrigger
interface, as follows:
stubFinder.trigger('return_book_1')
That will send out a message to the destination described in the output message of the contract.