92. Spring Cloud Contract Verifier Messaging
Spring Cloud Contract Verifier 允许您验证使用消息传递作为通信方式的 applications。本文档中显示的所有集成都与 Spring 一起使用,但您也可以创建自己的集成并使用它。
92.1 整合
您可以使用以下四种 integration 配置之一:
-
Apache Camel
-
Spring Integration
-
Spring Cloud Stream
-
Spring AMQP
由于我们使用 Spring Boot,如果您已将其中一个 libraries 添加到 classpath,则会自动设置所有消息传递 configuration。
记得将
@AutoConfigureMessageVerifier
放在生成的测试的 base class 上。否则,Spring Cloud Contract Verifier 的消息传递部分不起作用。
如果你想使用 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>
摇篮.
testCompile "org.springframework.cloud:spring-cloud-stream-test-support"
92.2 手册 Integration 测试
测试使用的主要接口是org.springframework.cloud.contract.verifier.messaging.MessageVerifier
。它定义了如何发送和接收消息。您可以创建自己的 implementation 来实现相同的目标。
在测试中,您可以 inject ContractVerifierMessageExchange
发送和接收 contract 后面的消息。然后将@AutoConfigureMessageVerifier
添加到您的测试中。这是一个 example:
@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureMessageVerifier
public static class MessagingContractTests {
@Autowired
private MessageVerifier verifier;
...
}
如果您的测试也需要存根,则
@AutoConfigureStubRunner
包含消息传递 configuration,因此您只需要一个注释。
92.3 Publisher-Side 测试生成
在 DSL 中使用input
或outputMessage
部分会导致在发布者端创建测试。默认情况下,会创建 JUnit 测试。但是,也有可能创建 Spock 测试。
我们应该考虑以下三种主要方案:
-
场景 1:没有输出消息产生输出消息。输出消息由 application 中的 component 触发(对于 example,scheduler)。
-
场景 2:输入消息触发输出消息。
-
场景 3:消耗输入消息,没有输出消息。
传递给
messageFrom
或sentTo
的目标对于不同的消息传递_implement 可以有不同的含义。对于流和积分,它首先被解析为destination
的 channel。然后,如果没有这样的destination
,则将其解析为 channel name。对于Camel,这是一个特定的 component(对于 example,jms
)。
92.3.1 场景 1:无输入消息
对于给定的 contract:
Groovy DSL.
def contractDsl = Contract.make {
label 'some_label'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('activemq:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
messagingContentType(applicationJson())
}
}
}
YAML.
label: some_label
input:
triggeredBy: bookReturnedTriggered
outputMessage:
sentTo: activemq:output
body:
bookName: foo
headers:
BOOK-NAME: foo
contentType: application/json
创建以下 JUnit 测试:
'''
// when:
bookReturnedTriggered();
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("activemq:output");
assertThat(response).isNotNull();
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 测试:
'''
when:
bookReturnedTriggered()
then:
ContractVerifierMessage response = contractVerifierMessaging.receive('activemq:output')
assert response != null
response.getHeader('BOOK-NAME')?.toString() == 'foo'
response.getHeader('contentType')?.toString() == 'application/json'
and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
'''
92.3.2 场景 2:输入触发的输出
对于给定的 contract:
Groovy DSL.
def contractDsl = Contract.make {
label 'some_label'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
YAML.
label: some_label
input:
messageFrom: jms:input
messageBody:
bookName: 'foo'
messageHeaders:
sample: header
outputMessage:
sentTo: jms:output
body:
bookName: foo
headers:
BOOK-NAME: foo
创建以下 JUnit 测试:
'''
// given:
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
"{\\"bookName\\":\\"foo\\"}"
, headers()
.header("sample", "header"));
// when:
contractVerifierMessaging.send(inputMessage, "jms:input");
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("jms:output");
assertThat(response).isNotNull();
assertThat(response.getHeader("BOOK-NAME")).isNotNull();
assertThat(response.getHeader("BOOK-NAME").toString()).isEqualTo("foo");
// and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()));
assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
'''
并将创建以下 Spock 测试:
"""\
given:
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
'''{"bookName":"foo"}''',
['sample': 'header']
)
when:
contractVerifierMessaging.send(inputMessage, 'jms:input')
then:
ContractVerifierMessage response = contractVerifierMessaging.receive('jms:output')
assert response !- null
response.getHeader('BOOK-NAME')?.toString() == 'foo'
and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
"""
92.3.3 场景 3:无输出消息
对于给定的 contract:
Groovy DSL.
def contractDsl = Contract.make {
label 'some_label'
input {
messageFrom('jms:delete')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
assertThat('bookWasDeleted()')
}
}
YAML.
label: some_label
input:
messageFrom: jms:delete
messageBody:
bookName: 'foo'
messageHeaders:
sample: header
assertThat: bookWasDeleted()
创建以下 JUnit 测试:
'''
// given:
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
"{\\"bookName\\":\\"foo\\"}"
, headers()
.header("sample", "header"));
// when:
contractVerifierMessaging.send(inputMessage, "jms:delete");
// then:
bookWasDeleted();
'''
并将创建以下 Spock 测试:
'''
given:
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
\'\'\'{"bookName":"foo"}\'\'\',
['sample': 'header']
)
when:
contractVerifierMessaging.send(inputMessage, 'jms:delete')
then:
noExceptionThrown()
bookWasDeleted()
'''
92.4 Consumer Stub Generation
与 HTTP 部分不同,在消息传递中,我们需要使用存根在 JAR 中发布 Groovy DSL。然后在 consumer 端解析它,并创建适当的 stubbed routes。
有关更多信息,请参阅第 94 章,消息传递的 Stub Runner部分。
Maven 的.
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
摇篮.
ext {
contractsDir = file("mappings")
stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
}
// Automatically added by plugin:
// copyContracts - copies contracts to the output folder from which JAR will be created
// verifierStubsJar - JAR with a provided stub suffix
// the presented publication is also added by the plugin but you can modify it as you wish
publishing {
publications {
stubs(MavenPublication) {
artifactId "${project.name}-stubs"
artifact verifierStubsJar
}
}
}