此版本仍在开发中,目前尚不被视为稳定版本。如需最新稳定版本,请使用 spring-cloud-contract 5.0.2spring-doc.cadn.net.cn

消息传递

Spring Cloud Contract 允许您验证那些使用消息传递作为通信方式的应用程序。本文档中显示的所有集成都与 Spring 兼容,但您也可以创建自己的集成并加以使用。spring-doc.cadn.net.cn

消息处理 DSL 顶层元素

用于消息传递的DSL(领域特定语言)与专注于HTTP的DSL略有不同。以下各节将解释其中的差异:spring-doc.cadn.net.cn

由方法触发的输出

输出消息可以通过调用一个方法(例如在合同启动时和发送消息时调用 Scheduler)来触发,如下例所示:spring-doc.cadn.net.cn

Groovy
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')
		}
	}
}
YAML
# 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

在前面的例子中,如果调用一个名为bookReturnedTriggered的方法,则输出消息将发送到output。在消息发布者的端,我们生成一个测试,该测试会调用该方法以触发消息。在消费者端,您可以使用some_label来触发消息。spring-doc.cadn.net.cn

消费者/生产者

此部分仅适用于 Groovy DSL。

在 HTTP 中,您有 client/stub and `server/test 的表示法。您同样也可以在消息传递中使用这些范式。此外,Spring Cloud Contract Verifier 还提供了 consumerproducer 方法(注意:您可以选择使用 $value 方法来提供 consumerproducer 部分)。spring-doc.cadn.net.cn

通用

inputoutputMessage 部分中,您可以调用 assertThat,并传入一个 method 的名称(例如 assertThatMessageIsOnTheQueue()),该名称已在基类或静态导入中定义。Spring Cloud Contract 会在生成的测试中运行该方法。spring-doc.cadn.net.cn

集成

您可以使用以下任一集成配置:spring-doc.cadn.net.cn

由于我们使用 Spring Boot,如果您已将以下任一库添加到类路径中,则所有消息传递配置都会自动设置。spring-doc.cadn.net.cn

请记得在生成的测试的基类中放置 @AutoConfigureMessageVerifier。否则,Spring Cloud Contract 的消息传递部分将无法正常工作。

如果您想使用 Spring Cloud Stream,请记得添加一个测试依赖 org.springframework.cloud:spring-cloud-stream,如下所示:spring-doc.cadn.net.cn

Maven
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream</artifactId>
    <type>test-jar</type>
    <scope>test</scope>
    <classifier>test-binder</classifier>
</dependency>
Gradle
testImplementation(group: 'org.springframework.cloud', name: 'spring-cloud-stream', classifier: 'test-binder')

手动集成测试

测试使用的主要接口是org.springframework.cloud.contract.verifier.messaging.MessageVerifierSenderorg.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver。它定义了如何发送和接收消息。spring-doc.cadn.net.cn

在测试中,您可以注入一个 ContractVerifierMessageExchange 来发送和接收符合协议的消息。然后将 @AutoConfigureMessageVerifier 添加到您的测试中。以下示例展示了如何操作:spring-doc.cadn.net.cn

@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureMessageVerifier
public static class MessagingContractTests {

  @Autowired
  private MessageVerifier verifier;
  ...
}
如果您的测试也需要桩(stubs),那么 @AutoConfigureStubRunner 包含了消息配置,因此您只需使用一个注解即可。

生产者端消息测试生成

在您的 DSL 中包含 inputoutputMessage 部分会导致在发布方侧生成测试。默认情况下,会创建 JUnit 4 测试。不过,您也可以选择创建 JUnit 5、TestNG 或 Spock 测试。spring-doc.cadn.net.cn

传递给 messageFromsentTo 的目标对于不同的消息传递实现可能具有不同的含义。对于流(Stream)和集成(Integration),它首先被解析为通道(channel)的 destination。然后,如果没有这样的 destination,它将被解析为通道名称。对于 Camel,这表示某种组件(例如,jms)。

考虑以下契约:spring-doc.cadn.net.cn

Groovy
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())
		}
	}
}
YAML
label: some_label
input:
  triggeredBy: bookReturnedTriggered
outputMessage:
  sentTo: activemq:output
  body:
    bookName: foo
  headers:
    BOOK-NAME: foo
    contentType: application/json

对于前面的例子,将创建以下测试:spring-doc.cadn.net.cn

JUnit
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.Test;
import org.junit.Rule;
import org.springframework.beans.factory.annotation.Autowired;
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 {
	@Autowired ContractVerifierMessaging contractVerifierMessaging;
	@Autowired 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 org.springframework.beans.factory.annotation.Autowired
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 {
	@Autowired ContractVerifierMessaging contractVerifierMessaging
	@Autowired 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")
	}

}

消费者存根生成

与 HTTP 部分不同,在消息传递中,我们需要将契约定义发布到 JAR 文件中,并附带一个存根(stub)。随后在消费者端对该契约进行解析,并创建相应的存根路由。spring-doc.cadn.net.cn

如果您在类路径中包含多个框架,Stub Runner 需要指定应使用哪一个。假设您的类路径中包含 AMQP、Spring Cloud Stream 和 Spring Integration,并且您希望使用 Spring AMQP。那么您需要设置 spring.cloud.contract.stubrunner.stream.enabled=falsespring.cloud.contract.stubrunner.integration.enabled=false。这样,剩下的唯一框架就是 Spring AMQP。

存根触发

要触发一条消息,请使用 StubTrigger 接口,如下例所示:spring-doc.cadn.net.cn

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

}

为了方便起见,StubFinder 接口扩展了 StubTrigger,因此在您的测试中只需使用其中一个即可。spring-doc.cadn.net.cn

StubTrigger 为您提供了以下选项来触发消息:spring-doc.cadn.net.cn

按标签触发

以下示例展示了如何通过标签触发消息:spring-doc.cadn.net.cn

stubFinder.trigger('return_book_1')

按组ID和构件ID触发

<p>以下示例展示了如何通过组和依赖包ID触发消息:</p>spring-doc.cadn.net.cn

stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:streamService', 'return_book_1')

通过构件 ID 触发

下面的示例显示如何从 artifact IDs 触发消息。spring-doc.cadn.net.cn

stubFinder.trigger('streamService', 'return_book_1')

触发所有消息

推写:Spring服勒器类与BeanFactory接口spring-doc.cadn.net.cn

stubFinder.trigger()

带有 Apache Camel 的消费端消息传递

Spring Cloud Contract Stub Runner 的消息传递模块为您提供了一种轻松集成 Apache Camel 的方法。<br/> 对于提供的工件,它会自动下载存档并注册所需的路由。<br/>spring-doc.cadn.net.cn

添加Apache Camel到项目

您可以拥有 Apache Camel 和 Spring Cloud Contract Stub Runner 在类路径上。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

记住用 @AutoConfigureStubRunner 标记您的测试类。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

禁用功能

如果要禁用此功能,请设置spring.cloud.contract.stubrunner.camel.enabled=false属性。spring-doc.cadn.net.cn

示例

假设我们有以下带有部署的存档存档的Maven存储库对于应用软件camelService:spring-doc.cadn.net.cn

└── .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

此外,假设存档包含以下结构:spring-doc.cadn.net.cn

├── META-INF
│   └── MANIFEST.MF
└── repository
    ├── accurest
    │   └── bookReturned1.groovy
    └── mappings

现在考虑以下合同:spring-doc.cadn.net.cn

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接口,如下所示:spring-doc.cadn.net.cn

stubFinder.trigger("return_book_1")

那会向合约输出消息中描述的目的地址发送一条消息。spring-doc.cadn.net.cn

带有 Spring 集成的消息传递(消费方)

Spring Cloud Contract Stub Runner 的 messaging 模块为您提供一种轻松与 Spring Integration 集成的方式。对于提供的构件,它会自动下载 stubs 并注册所需的路由。spring-doc.cadn.net.cn

<h2>向项目添加运行程序</h2>

您可以同时在类路径上拥有Spring Integration和Spring Cloud Contract Stub Runner。请记住,将@AutoConfigureStubRunner注解添加到您的测试类。spring-doc.cadn.net.cn

禁用功能

如果需要禁用此功能,请将 spring.cloud.contract.stubrunner.integration.enabled=false 属性设置。spring-doc.cadn.net.cn

示例

假设您有带有为应用程序integrationService部署的存档存档的以下Maven存储库:spring-doc.cadn.net.cn

└── .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

假设存根包含以下结构:spring-doc.cadn.net.cn

├── META-INF
│   └── MANIFEST.MF
└── repository
    ├── accurest
    │   └── bookReturned1.groovy
    └── mappings

考虑以下契约:spring-doc.cadn.net.cn

Contract.make {
	label 'return_book_1'
	input {
		triggeredBy('bookReturnedTriggered()')
	}
	outputMessage {
		sentTo('output')
		body('''{ "bookName" : "foo" }''')
		headers {
			header('BOOK-NAME', 'foo')
		}
	}
}

现在考虑以下 Spring 集成路由:spring-doc.cadn.net.cn

<?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 接口,如下所示:spring-doc.cadn.net.cn

stubFinder.trigger('return_book_1')

那会向合约输出消息中描述的目的地址发送一条消息。spring-doc.cadn.net.cn

使用Spring Cloud Stream的消息传递侧

模块为 Spring Cloud Contract Stub Runner 提供了与 Spring Stream 集成的便捷方式。对于提供的工件,它会自动下载桩并注册所需的路由。spring-doc.cadn.net.cn

如果 Stub Runner 的集成与 Stream messageFromsentTo 字符串首先解决为 destination 通道的,那么目标被解析为通道名称。

如果要使用Spring Cloud Stream,请记住添加对org.springframework.cloud:spring-cloud-stream测试支持的依赖项,如下所示:spring-doc.cadn.net.cn

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')

<h2>向项目添加运行程序</h2>

你可以在类路径中同时拥有 Spring Cloud Stream 和 Spring Cloud Contract Stub Runner。记住,在测试类上用 @AutoConfigureStubRunner 进行注释。spring-doc.cadn.net.cn

禁用功能

如果需要禁用此功能,请设置spring.cloud.contract.stubrunner.stream.enabled=false属性。spring-doc.cadn.net.cn

示例

假设您有带有为应用程序streamService部署的存档存档的以下Maven存储库:spring-doc.cadn.net.cn

└── .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

假设存根包含以下结构:spring-doc.cadn.net.cn

├── META-INF
│   └── MANIFEST.MF
└── repository
    ├── accurest
    │   └── bookReturned1.groovy
    └── mappings

考虑以下契约:spring-doc.cadn.net.cn

Contract.make {
	label 'return_book_1'
	input { triggeredBy('bookReturnedTriggered()') }
	outputMessage {
		sentTo('returnBook')
		body('''{ "bookName" : "foo" }''')
		headers { header('BOOK-NAME', 'foo') }
	}
}

现在考虑以下Spring Cloud Stream函数配置:spring-doc.cadn.net.cn

@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配置:spring-doc.cadn.net.cn

spring.cloud.contract.stubrunner.repositoryRoot: classpath:m2repo/repository/
spring.cloud.contract.stubrunner.ids: org.springframework.cloud.contract.verifier.stubs:streamService:0.0.1-SNAPSHOT:stubs
spring.cloud.contract.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接口:spring-doc.cadn.net.cn

stubFinder.trigger('return_book_1')

那会向合约输出消息中描述的目的地址发送一条消息。spring-doc.cadn.net.cn

Spring框架中的消费者端消息传递与Spring JMS

Spring Cloud Contract Stub Runner 的 messaging 模块提供了一种简单的集成方式,用于与 Spring JMS。spring-doc.cadn.net.cn

该集成假定您已经运行了一个JMS代理实例。spring-doc.cadn.net.cn

<h2>向项目添加运行程序</h2>

您需要同时具有 Spring JMS 和 Spring Cloud Contract Stub Runner 在类路径中。请记住用< code >注释您的测试类0。。spring-doc.cadn.net.cn

示例

假设存根结构如下:spring-doc.cadn.net.cn

├── stubs
    └── bookReturned1.groovy

进一步假设以下测试配置:spring-doc.cadn.net.cn

spring.cloud.contract.stubrunner:
  repository-root: stubs:classpath:/stubs/
  ids: my:stubs
  stubs-mode: remote
spring:
  activemq:
    send-timeout: 1000
  jms:
    template:
      receive-timeout: 1000

现在考虑以下合同:spring-doc.cadn.net.cn

Contract.make {
	label 'return_book_1'
	input {
		triggeredBy('bookReturnedTriggered()')
	}
	outputMessage {
		sentTo('output')
		body('''{ "bookName" : "foo" }''')
		headers {
			header('BOOKNAME', 'foo')
		}
	}
}

要从return_book_1标签触发消息,我们使用StubTrigger接口,如下所示:spring-doc.cadn.net.cn

stubFinder.trigger('return_book_1')

那会向合约输出消息中描述的目的地址发送一条消息。spring-doc.cadn.net.cn