訊息傳遞
Spring Cloud Contract 讓您可以驗證使用訊息傳遞作為溝通方式的應用程式。本文檔中顯示的所有整合都適用於 Spring,但您也可以建立自己的整合並使用它。
訊息傳遞 DSL 頂層元素
訊息傳遞的 DSL 看起來與專注於 HTTP 的 DSL 略有不同。以下各節將說明差異之處
由方法觸發的輸出
輸出訊息可以透過呼叫方法(例如,當契約開始且訊息已發送時的 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
在先前的範例案例中,如果呼叫名為 bookReturnedTriggered
的方法,則輸出訊息會傳送到 output
。在訊息發佈者端,我們產生一個測試,呼叫該方法以觸發訊息。在消費者端,您可以使用 some_label
來觸發訊息。
整合
您可以使用以下整合組態之一
-
Apache Camel
-
Spring Integration
-
Spring Cloud Stream
-
Spring JMS
由於我們使用 Spring Boot,如果您已將這些程式庫之一新增至類別路徑,則所有訊息傳遞組態都會自動設定。
請記住在產生的測試的基底類別上放置 @AutoConfigureMessageVerifier 。否則,Spring Cloud Contract 的訊息傳遞部分將無法運作。 |
如果您想要使用 Spring Cloud Stream,請記住新增 Maven
Gradle
|
手動整合測試
測試使用的主要介面是 org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender
和 org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver
。它定義了如何發送和接收訊息。
在測試中,您可以注入 ContractVerifierMessageExchange
以發送和接收遵循契約的訊息。然後將 @AutoConfigureMessageVerifier
新增至您的測試。以下範例顯示如何執行此操作
@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureMessageVerifier
public static class MessagingContractTests {
@Autowired
private MessageVerifier verifier;
...
}
如果您的測試也需要 Stub,則 @AutoConfigureStubRunner 會包含訊息傳遞組態,因此您只需要一個註解。 |
生產者端訊息傳遞測試產生
在您的 DSL 中具有 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");
}
}
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")
}
}
消費者 Stub 產生
與 HTTP 部分不同,在訊息傳遞中,我們需要在 JAR 內發佈契約定義以及 Stub。然後在消費者端解析它,並建立適當的 Stub 路由。
如果您在類別路徑上有多個框架,Stub Runner 需要定義應使用哪個框架。假設您的類別路徑上有 AMQP、Spring Cloud Stream 和 Spring Integration,並且您想要使用 Spring AMQP。然後您需要設定 stubrunner.stream.enabled=false 和 stubrunner.integration.enabled=false 。這樣,唯一剩下的框架就是 Spring AMQP。 |
Stub 觸發
若要觸發訊息,請使用 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();
}
為了方便起見,StubFinder
介面擴展了 StubTrigger
,因此您的測試中只需要其中一個即可。
StubTrigger
為您提供以下選項來觸發訊息
搭配 Apache Camel 的消費者端訊息傳遞
Spring Cloud Contract Stub Runner 的訊息傳遞模組讓您可以輕鬆地與 Apache Camel 整合。對於提供的 Artifact,它會自動下載 Stub 並註冊所需的路由。
將 Apache Camel 新增至專案
您可以在類別路徑上同時擁有 Apache Camel 和 Spring Cloud Contract Stub Runner。請記住使用 @AutoConfigureStubRunner
註解您的測試類別。
範例
假設我們有以下 Maven 儲存庫,其中部署了 camelService
應用程式的 Stub
└── .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
此外,假設 Stub 包含以下結構
├── 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")
這將發送訊息到契約的輸出訊息中描述的目的地。
搭配 Spring Integration 的消費者端訊息傳遞
Spring Cloud Contract Stub Runner 的訊息傳遞模組讓您可以輕鬆地與 Spring Integration 整合。對於提供的 Artifact,它會自動下載 Stub 並註冊所需的路由。
將 Runner 新增至專案
您可以在類別路徑上同時擁有 Spring Integration 和 Spring Cloud Contract Stub Runner。請記住使用 @AutoConfigureStubRunner
註解您的測試類別。
範例
假設您有以下 Maven 儲存庫,其中部署了 integrationService
應用程式的 Stub
└── .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
此外,假設 Stub 包含以下結構
├── 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 Integration 路由
<?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')
這將發送訊息到契約的輸出訊息中描述的目的地。
搭配 Spring Cloud Stream 的消費者端訊息傳遞
Spring Cloud Contract Stub Runner 的訊息傳遞模組讓您可以輕鬆地與 Spring Stream 整合。對於提供的 Artifact,它會自動下載 Stub 並註冊所需的路由。
如果 Stub Runner 與 Stream 的整合 messageFrom 或 sentTo 字串首先解析為通道的 destination ,且不存在此類 destination ,則目的地會解析為通道名稱。 |
如果您想要使用 Spring Cloud Stream,請記住新增 Maven
Gradle
|
將 Runner 新增至專案
您可以在類別路徑上同時擁有 Spring Cloud Stream 和 Spring Cloud Contract Stub Runner。請記住使用 @AutoConfigureStubRunner
註解您的測試類別。
範例
假設您有以下 Maven 儲存庫,其中部署了 streamService
應用程式的 Stub
└── .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
此外,假設 Stub 包含以下結構
├── 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')
這將發送訊息到契約的輸出訊息中描述的目的地。
搭配 Spring JMS 的消費者端訊息傳遞
Spring Cloud Contract Stub Runner 的訊息傳遞模組提供了一種輕鬆與 Spring JMS 整合的方式。
此整合假設您有一個正在執行的 JMS Broker 實例。
將 Runner 新增至專案
您需要在類別路徑上同時擁有 Spring JMS 和 Spring Cloud Contract Stub Runner。請記住使用 @AutoConfigureStubRunner
註解您的測試類別。
範例
假設 Stub 結構如下所示
├── 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')
這將發送訊息到契約的輸出訊息中描述的目的地。