此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 spring-cloud-contract 4.2.0spring-doc.cadn.net.cn

消息

Spring Cloud Contract 允许您验证使用消息传递作为 通讯方式。本文档中展示的所有集成都可以与 Spring 一起使用。 但你也可以创建自己的一个并使用它。spring-doc.cadn.net.cn

消息传递 DSL 顶级元素

用于消息传递的 DSL 看起来与专注于 HTTP 的 DSL 略有不同。这 以下部分解释了差异:spring-doc.cadn.net.cn

由方法触发的输出

输出消息可以通过调用方法(例如Scheduler当合约是 started 和发送消息时),如以下示例所示:spring-doc.cadn.net.cn

槽的
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

在前面的示例案例中,输出消息被发送到output如果调用了bookReturnedTriggered被调用。在消息发布者方面,我们生成一个 test 调用该方法来触发消息。在消费者方面,您可以使用some_label以触发消息。spring-doc.cadn.net.cn

消费者/生产者

此部分仅对 Groovy DSL 有效。

在 HTTP 中,您有一个client/stub and `server/test表示法。您还可以 在消息传递中使用这些范例。此外,Spring Cloud Contract Verifier 还 提供consumerproducer方法 (请注意,您可以使用 OR$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,如果您已将这些库之一添加到 Classpath 中,则所有 消息收发配置是自动设置的。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发送和接收 遵循 Contract 的消息。然后添加@AutoConfigureMessageVerifier进行测试。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

  @Autowired
  private MessageVerifier verifier;
  ...
}
如果您的测试也需要存根,则@AutoConfigureStubRunner包括 消息传送配置,因此您只需要一个注释。

生产者端消息收发测试生成

使用inputoutputMessage部分会导致创建测试 在出版商方面。默认情况下,将创建 JUnit 4 测试。但是,还有一个 可以创建 JUnit 5、TestNG 或 Spock 测试。spring-doc.cadn.net.cn

传递给messageFromsentTo可以具有不同的 不同消息传递实现的含义。对于 Stream 和 Integration,它是 首先解析为destination的频道。然后,如果没有这样的destination, 它被解析为通道名称。对于 Camel 来说,这是一个特定的组件(例如,jms).

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

槽的
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 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.spring-doc.cadn.net.cn

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

}

For convenience, the StubFinder interface extends StubTrigger, so you need only one or the other in your tests.spring-doc.cadn.net.cn

StubTrigger gives you the following options to trigger a message:spring-doc.cadn.net.cn

Trigger by Label

The following example shows how to trigger a message with a label:spring-doc.cadn.net.cn

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

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

Trigger by Artifact IDs

The following example shows how to trigger a message from artifact IDs:spring-doc.cadn.net.cn

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

Trigger All Messages

The following example shows how to trigger all messages:spring-doc.cadn.net.cn

stubFinder.trigger()

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.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Disabling the Functionality

If you need to disable this functionality, set the stubrunner.camel.enabled=false property.spring-doc.cadn.net.cn

Examples

Assume that we have the following Maven repository with deployed stubs for the camelService application: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

Further, assume that the stubs contain the following structure:spring-doc.cadn.net.cn

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

Now consider the following contract: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')
		}
	}
}

To trigger a message from the return_book_1 label, we use the StubTrigger interface, as follows:spring-doc.cadn.net.cn

stubFinder.trigger("return_book_1")

That will send out a message to the destination described in the output message of the contract.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Disabling the Functionality

If you need to disable this functionality, set the stubrunner.integration.enabled=false property.spring-doc.cadn.net.cn

Examples

Assume that you have the following Maven repository with deployed stubs for the integrationService application: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

Further assume the stubs contain the following structure:spring-doc.cadn.net.cn

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

Consider the following contract: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')
		}
	}
}

Now consider the following Spring Integration Route: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>

To trigger a message from the return_book_1 label, use the StubTrigger interface, as follows:spring-doc.cadn.net.cn

stubFinder.trigger('return_book_1')

That will send out a message to the destination described in the output message of the contract.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

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

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.spring-doc.cadn.net.cn

Disabling the Functionality

If you need to disable this functionality, set the stubrunner.stream.enabled=false property.spring-doc.cadn.net.cn

Examples

Assume that you have the following Maven repository with deployed stubs for the streamService application: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

Further assume the stubs contain the following structure:spring-doc.cadn.net.cn

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

Consider the following contract: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') }
	}
}

Now consider the following Spring Cloud Stream function configuration: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
		}
	}

}

Now consider the following Spring configuration:spring-doc.cadn.net.cn

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

stubFinder.trigger('return_book_1')

That will send out a message to the destination described in the output message of the contract.spring-doc.cadn.net.cn

Consumer Side Messaging With Spring JMS

Spring Cloud Contract Stub Runner’s messaging module provides an easy way to integrate with Spring JMS.spring-doc.cadn.net.cn

The integration assumes that you have a running instance of a JMS broker.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Examples

Assume that the stub structure looks as follows:spring-doc.cadn.net.cn

├── stubs
    └── bookReturned1.groovy

Further assume the following test configuration:spring-doc.cadn.net.cn

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

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

stubFinder.trigger('return_book_1')

That will send out a message to the destination described in the output message of the contract.spring-doc.cadn.net.cn