Spring Cloud 合约功能(三)_spring

4.4. 消费者存根生成

与 HTTP 部分不同,在消息传递中,我们需要在 JAR 中发布合约定义 一个存根。然后在消费者端解析它,并创建适当的存根路由。

如果类路径上有多个框架,则存根运行程序需要 定义应使用哪一个。假设你有AMQP、Spring Cloud Stream和Spring Integration。 在类路径上,并且您想要使用 Spring AMQP。然后你需要设置。 这样,唯一剩下的框架就是Spring AMQP。​​stubrunner.stream.enabled=false​​​​stubrunner.integration.enabled=false​

4.4.1. 存根触发

要触发消息,请使用接口,如以下示例所示:​​StubTrigger​

package org.springframework.cloud.contract.stubrunner;

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​​提供以下选项来触发消息:

  • 按标签触发
  • 按组和项目 ID 触发
  • 由项目 ID 触发
  • 触发所有消息

4.4.2. 标签触发

以下示例演示如何触发带有标签的消息:

stubFinder.trigger('return_book_1')

4.4.3. 按组和工件 ID 触发

以下示例演示如何按组和项目 ID 触发消息:

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

4.4.4. 由工件 ID 触发

以下示例演示如何从项目 ID 触发消息:

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

4.4.5. 触发所有消息

以下示例演示如何触发所有消息:

stubFinder.trigger()

4.5. 使用 Apache Camel 的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块为您提供了一种与 Apache Camel 集成的简单方法。 对于提供的工件,它会自动下载存根并注册所需的 路线。

4.5.1. 将 Apache Camel 添加到项目中

您可以在类路径上同时拥有 Apache Camel 和 Spring Cloud 合约存根运行程序。 记得用注释你的测试类。​​@AutoConfigureStubRunner​

4.5.2. 禁用该功能

如果需要禁用此功能,请设置该属性。​​stubrunner.camel.enabled=false​

4.5.3. 例子

假设我们有以下 Maven 存储库,其中包含为应用程序部署的存根:​​camelService​

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

进一步假设存根包含以下结构:

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

现在考虑以下合同(我们将其编号为 1 和 2):

Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('jms:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
Contract.make {
label 'return_book_2'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}

这些示例适用于三种方案:

  1. 方案 1(无输入消息)
  2. 场景 2(由输入触发的输出)
  3. 场景 3(无输出的输入)
方案 1(无输入消息)

要触发来自标签的消息,我们使用接口,如下所示:​​return_book_1​​​​StubTrigger​

stubFinder.trigger('return_book_1')

接下来,我们要侦听发送到以下消息的输出:​​jms:output​

Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000)

然后,收到的消息将传递以下断言:

receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
场景 2(由输入触发的输出)

由于路由已为您设置,因此您可以向目的地发送消息。​​jms:output​

producerTemplate.
sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])

接下来,我们要监听发送到的消息的输出,如下所示:​​jms:output​

Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000)

收到的消息将传递以下断言:

receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
场景 3(无输出的输入)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​jms:output​

producerTemplate.
sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header'])

4.6. 带有 Spring 集成的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块为您提供了一种简单的方法 与弹簧集成集成。对于提供的工件,它会自动下载 存根并注册所需的路由。

4.6.1. 将运行器添加到项目中

您可以在 类路径。记得用注释你的测试类。​​@AutoConfigureStubRunner​

4.6.2. 禁用该功能

如果需要禁用此功能,请设置该属性。​​stubrunner.integration.enabled=false​

4.6.3. 例子

假设您有以下 Maven 存储库,其中包含为应用程序部署的存根:​​integrationService​

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

进一步假设存根包含以下结构:

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

考虑以下合同(编号为 1 和 2):

Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
Contract.make {
label 'return_book_2'
input {
messageFrom('input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}

现在考虑以下 Spring 集成路线:

<?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>

这些示例适用于三种方案:

  1. 方案 1(无输入消息)
  2. 场景 2(由输入触发的输出)
  3. 场景 3(无输出的输入)
方案 1(无输入消息)

要从标签触发消息,请使用接口,作为 遵循:​​return_book_1​​​​StubTrigger​

stubFinder.trigger('return_book_1')

下面的清单显示了如何侦听发送到的消息的输出:​​jms:output​

Message<?> receivedMessage = messaging.receive('outputTest')

收到的消息将传递以下断言:

receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 2(由输入触发的输出)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​jms:output​

messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')

下面的清单显示了如何侦听发送到的消息的输出:​​jms:output​

Message<?> receivedMessage = messaging.receive('outputTest')

收到的消息传递以下断言:

receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 3(无输出的输入)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​jms:input​

messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')

4.7. 使用弹簧云流的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块为您提供了一种简单的方法 与春溪融合。对于提供的工件,它会自动下载 存根并注册所需的路由。

如果存根运行程序与流字符串的集成 首先解析为通道,不存在这样的通道, 目标解析为通道名称。​​messageFrom​​​​sentTo​​​​destination​​​​destination​


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






马文

格拉德尔



<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>





4.7.1. 将运行器添加到项目中

您可以在 类路径。记得用注释你的测试类。​​@AutoConfigureStubRunner​

4.7.2. 禁用该功能

如果需要禁用此功能,请设置该属性。​​stubrunner.stream.enabled=false​

4.7.3. 例子

假设您有以下 Maven 存储库,其中包含为应用程序部署的存根:​​streamService​

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

进一步假设存根包含以下结构:

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

考虑以下合同(编号为 1 和 2):

Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
Contract.make {
label 'return_book_2'
input {
messageFrom('bookStorage')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
}
outputMessage {
sentTo('returnBook')
body([
bookName: 'foo'
])
headers { header('BOOK-NAME', 'foo') }
}
}

现在考虑以下 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:
output:
destination: returnBook
input:
destination: bookStorage

server:
port: 0

debug: true

这些示例适用于三种方案:

  • 方案 1(无输入消息)
  • 场景 2(由输入触发的输出)
  • 场景 3(无输出的输入)
方案 1(无输入消息)

要从标签触发消息,请使用接口作为 遵循:​​return_book_1​​​​StubTrigger​

stubFinder.trigger('return_book_1')

以下示例演示如何侦听发送到通道 whoseis 的消息输出:​​destination​​​​returnBook​

Message<?> receivedMessage = messaging.receive('returnBook')

收到的消息传递以下断言:

receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 2(由输入触发的输出)

由于路由已为您设置,因此您可以向 发送消息,如下所示:​​bookStorage​​​​destination​

messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')

以下示例演示如何侦听发送到的消息的输出:​​returnBook​

Message<?> receivedMessage = messaging.receive('returnBook')

收到的消息传递以下断言:

receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
场景 3(无输出的输入)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​jms:output​

messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')

4.8. 使用 Spring AMQP 的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块提供了一种简单的方法 与Spring AMQP的兔子模板集成。对于提供的工件,它 自动下载存根并注册所需的路由。

集成尝试独立工作(即,不与正在运行的交互 RabbitMQ 消息代理)。它期望应用程序上下文和 将其用作名为的 Spring 启动测试。因此,它可以使用Mockito间谍 用于验证和检查应用程序发送的消息的功能。​​RabbitTemplate​​​​@SpyBean​

在消息使用者端,存根运行器认为是全注释的 应用程序上下文中的终结点和所有对象。​​@RabbitListener​​​​SimpleMessageListenerContainer​

由于消息通常发送到AMQP中的交易所,因此消息合约包含 交换名称作为目标。另一端的消息侦听器绑定到 队列。绑定将交换连接到队列。如果触发了消息协定,则 Spring AMQP 存根运行器集成在应用程序上下文中查找绑定 匹配此交换。然后它从 Spring 交易所收集队列并尝试 查找绑定到这些队列的消息侦听器。为所有匹配触发消息 消息侦听器。

如果需要使用路由密钥,可以使用消息标头传递它们。​​amqp_receivedRoutingKey​

4.8.1. 将运行器添加到项目中

您可以在类路径上同时拥有 Spring AMQP 和 Spring Cloud 合约存根运行程序,并且 设置属性。记得为您的测试类添加注释 跟。​​stubrunner.amqp.enabled=true​​​​@AutoConfigureStubRunner​

如果类路径上已有流和集成,则需要 通过设置属性来显式禁用它们。​​stubrunner.stream.enabled=false​​​​stubrunner.integration.enabled=false​

4.8.2. 例子

假设您有以下 Maven 存储库,其中包含为应用程序部署的存根:​​spring-cloud-contract-amqp-test​

└── .m2
└── repository
└── com
└── example
└── spring-cloud-contract-amqp-test
├── 0.4.0-SNAPSHOT
│ ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom
│ ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar
│ └── maven-metadata-local.xml
└── maven-metadata-local.xml

进一步假设存根包含以下结构:

├── META-INF
│ └── MANIFEST.MF
└── contracts
└── shouldProduceValidPersonData.groovy

然后考虑以下合同:

Contract.make {
// Human readable description
description 'Should produce valid person data'
// Label by means of which the output message can be triggered
label 'contract-test.person.created.event'
// input to the contract
input {
// the contract will be triggered by a method
triggeredBy('createPerson()')
}
// output message of the contract
outputMessage {
// destination to which the output message will be sent
sentTo 'contract-test.exchange'
headers {
header('contentType': 'application/json')
header('__TypeId__': 'org.springframework.cloud.contract.stubrunner.messaging.amqp.Person')
}
// the body of the output message
body([
id : $(consumer(9), producer(regex("[0-9]+"))),
name: "me"
])
}
}

现在考虑以下 Spring 配置:

stubrunner:
repositoryRoot: classpath:m2repo/repository/
ids: org.springframework.cloud.contract.verifier.stubs.amqp:spring-cloud-contract-amqp-test:0.4.0-SNAPSHOT:stubs
stubs-mode: remote
amqp:
enabled: true
server:
port: 0
触发消息

要使用上一节中的协定触发消息,请使用接口,如 遵循:​​StubTrigger​

stubTrigger.trigger("contract-test.person.created.event")

消息的目的地为 ,因此 Spring AMQP 存根运行程序 集成查找与此交换相关的绑定,如以下示例所示:​​contract-test.exchange​

@Bean
public Binding binding() {
return BindingBuilder.bind(new Queue("test.queue")).to(new DirectExchange("contract-test.exchange"))
.with("#");
}

绑定定义绑定调用的队列。结果,以下侦听器 定义与合约消息匹配和调用:​​test.queue​

@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("test.queue");
container.setMessageListener(listenerAdapter);

return container;
}

此外,以下带批注的侦听器匹配并被调用:

@RabbitListener(bindings = @QueueBinding(value = @Queue("test.queue"),
exchange = @Exchange(value = "contract-test.exchange", ignoreDeclarationExceptions = "true")))
public void handlePerson(Person person) {
this.person = person;
}

消息直接交给与匹配相关联的方法。​​onMessage​​​​MessageListener​​​​SimpleMessageListenerContainer​

春季 AMQP 测试配置

为了避免Spring AMQP在我们的测试期间试图连接到正在运行的代理,我们 配置模拟。​​ConnectionFactory​

要禁用模拟,请设置以下属性:,如下所示:​​ConnectionFactory​​​​stubrunner.amqp.mockConnection=false​

stubrunner:
amqp:
mockConnection: false

4.9. 使用 Spring JMS 的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块提供了一种简单的方法 与Spring JMS集成。

集成假定您有一个正在运行的 JMS 代理实例(例如嵌入式代理)。​​activemq​

4.9.1. 将运行器添加到项目中

你需要在类路径上同时拥有 Spring JMS 和 Spring Cloud 合约存根运行程序。记得为您的测试类添加注释 跟。​​@AutoConfigureStubRunner​

4.9.2. 例子

假设存根结构如下所示:

├── stubs
├── bookDeleted.groovy
├── bookReturned1.groovy
└── bookReturned2.groovy

进一步假设以下测试配置:

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

现在考虑以下合同(我们将其编号为 1 和 2):

Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
Contract.make {
label 'return_book_2'
input {
messageFrom('input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
方案 1(无输入消息)

要触发来自标签的消息,我们使用接口,如下所示:​​return_book_1​​​​StubTrigger​

stubFinder.trigger('return_book_1')

接下来,我们要侦听发送到以下消息的输出:​​output​

TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output')

然后,收到的消息将传递以下断言:

receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.getText())
receivedMessage.getStringProperty('BOOK-NAME') == 'foo'
场景 2(由输入触发的输出)

由于路由已为您设置,因此您可以向目的地发送消息。​​output​

jmsTemplate.
convertAndSend('input', new BookReturned('foo'), new MessagePostProcessor() {
@Override
Message postProcessMessage(Message message) throws JMSException {
message.setStringProperty("sample", "header")
return message
}
})

接下来,我们要监听发送到的消息的输出,如下所示:​​output​

TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output')

收到的消息将传递以下断言:

receivedMessage != null
assertThatBodyContainsBookNameFoo(receivedMessage.getText())
receivedMessage.getStringProperty('BOOK-NAME') == 'foo'
场景 3(无输出的输入)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​output​

jmsTemplate.
convertAndSend('delete', new BookReturned('foo'), new MessagePostProcessor() {
@Override
Message postProcessMessage(Message message) throws JMSException {
message.setStringProperty("sample", "header")
return message
}
})

4.10. 使用 Spring Kafka 的消费者端消息传递

Spring Cloud 合约存根运行器的消息传递模块提供了一种简单的方法 与春天的卡夫卡集成。

集成假定您有一个嵌入式 Kafka 代理的运行实例(通过依赖关系)。spring-kafka-test

4.10.1. 将运行器添加到项目中

你需要在类路径上有 Spring Kafka、Spring Kafka 测试(运行)和 Spring Cloud 合约存根运行器。记得为您的测试类添加注释 跟。​​@EmbeddedBroker​​​​@AutoConfigureStubRunner​

通过 Kafka 集成,为了轮询单个消息,我们需要在 Spring 上下文启动时注册一个消费者。这可能会导致这样一种情况:当您在使用者端时,存根运行程序可以为同一组 ID 和主题注册额外的使用者。这可能会导致只有一个组件实际轮询消息的情况。由于在使用者端,您同时拥有 Spring Cloud 合约存根运行程序和 Spring Cloud 合约验证程序类路径,因此我们需要能够关闭此类行为。这是通过 theflag 自动完成的,这会禁用联系人验证程序使用者注册。如果应用程序既是 Kafka 消息的使用者又是生成者,则可能需要手动将该属性切换为生成的测试的基类。​​stubrunner.kafka.initializer.enabled​​​​false​

如果您有多个 bean,则可以提供自己的 bean 类型,以返回您选择的 bean。​​KafkaTemplate​​​​Supplier<KafkaTemplate>​​​​KafkaTemplate​

4.10.2. 例子

假设存根结构如下所示:

├── stubs
├── bookDeleted.groovy
├── bookReturned1.groovy
└── bookReturned2.groovy

进一步假设以下测试配置(注意指向嵌入式代理的 IP 通过):​​spring.kafka.bootstrap-servers​​​​${spring.embedded.kafka.brokers}​

stubrunner:
repository-root: stubs:classpath:/stubs/
ids: my:stubs
stubs-mode: remote
spring:
kafka:
bootstrap-servers: ${spring.embedded.kafka.brokers}
producer:
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
properties:
"spring.json.trusted.packages": "*"
consumer:
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
"spring.json.trusted.packages": "*"
group-id: groupId

如果您的应用程序使用非整数记录键,则需要相应地设置 and 属性,因为 Kafka 反序列化需要非空 记录键为整数类型。​​spring.kafka.producer.key-serializer​​​​spring.kafka.consumer.key-deserializer​

现在考虑以下合同(我们将其编号为 1 和 2):

Contract.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
Contract.make {
label 'return_book_2'
input {
messageFrom('input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
方案 1(无输入消息)

要触发来自标签的消息,我们使用接口,如下所示:​​return_book_1​​​​StubTrigger​

stubFinder.trigger('return_book_1')

接下来,我们要侦听发送到以下消息的输出:​​output​

Message receivedMessage = receiveFromOutput()

然后,收到的消息将传递以下断言:

assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
场景 2(由输入触发的输出)

由于路由已为您设置,因此您可以向目的地发送消息。​​output​

Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('input')
kafkaTemplate.send(message)
Message message = MessageBuilder.createMessage(new BookReturned('bar'), new MessageHeaders([kafka_messageKey: "bar5150",]))
kafkaTemplate.setDefaultTopic('input2')
kafkaTemplate.send(message)

接下来,我们要监听发送到的消息的输出,如下所示:​​output​

Message receivedMessage = receiveFromOutput()
Message receivedMessage = receiveFromOutput()

收到的消息将传递以下断言:

assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
assert receivedMessage != null
assert assertThatBodyContainsBookName(receivedMessage.getPayload(), 'bar')
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'bar'
assert receivedMessage.getHeaders().get("kafka_receivedMessageKey") == 'bar5150'
场景 3(无输出的输入)

由于路由已为您设置,因此您可以向目的地发送消息,如下所示:​​output​

Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('delete')
kafkaTemplate.send(message)

5. 春云合约存根跑者

使用Spring Cloud合约验证程序时可能遇到的问题之一是 将生成的 WireMock JSON 存根从服务器端传递到客户端(或 各种客户)。在消息传递的客户端生成方面也是如此。

复制 JSON 文件并手动设置消息传递的客户端不在 问题。这就是我们引入春云合约存根运行器的原因。它可以 自动下载并运行存根。

5.1. 快照版本

您可以将其他快照存储库添加到构建文件中以使用快照 版本,每次成功构建后都会自动上传,如下所示:

<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

5.2. 将存根发布为 JAR

将存根发布为 jar 的最简单方法是集中存根的保存方式。 例如,您可以将它们作为 jar 保存在 Maven 存储库中。

对于Maven和Gradle来说,设置已经准备好工作了。但是,您可以自定义 如果你愿意的话。

以下示例演示如何将存根发布为 jar:

<!-- First disable the default jar setup in the properties section -->
<!-- we don't want the verifier to do a jar for us -->
<spring.cloud.contract.verifier.skip>true</spring.cloud.contract.verifier.skip>

<!-- Next add the assembly plugin to your build -->
<!-- we want the assembly plugin to generate the JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>stub</id>
<phase>prepare-package</phase>
<goals>
<goal>single</goal>
</goals>
<inherited>false</inherited>
<configuration>
<attach>true</attach>
<descriptors>
$/tmp/releaser-1667490065708-0/spring-cloud-contract/docs/src/assembly/stub.xml
</descriptors>
</configuration>
</execution>
</executions>
</plugin>

<!-- Finally setup your assembly. Below you can find the contents of src/main/assembly/stub.xml -->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 https://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>stubs</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/java</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>**com/example/model/*.*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/classes</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>**com/example/model/*.*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/snippets/stubs</directory>
<outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>$/tmp/releaser-1667490065708-0/spring-cloud-contract/docs/src/test/resources/contracts</directory>
<outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory>
<includes>
<include>**/*.groovy</include>
</includes>
</fileSet>
</fileSets>
</assembly>

5.3. 短截流道核心

存根运行器核心为服务协作者运行存根。将存根视为 服务允许您使用存根运行程序作为消费者驱动合约的实现。

存根运行程序允许您自动下载提供的依赖项的存根(或 从类路径中选择那些),为它们启动 WireMock 服务器,并为它们提供适当的 存根定义。对于消息传递,定义了特殊的存根路由。

5.3.1. 检索存根

您可以从以下获取存根的选项中进行选择:

  • 基于以太的解决方案,可从Artifactory或Nexus下载带有存根的JAR。
  • 类路径扫描解决方案,它使用模式搜索类路径以检索存根
  • 编写自己的实现以进行完全自定义org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder

后一个示例在自定义存根运行程序部分中介绍。

下载存根

您可以使用开关控制存根的下载。它从枚举中选取值。您可以使用以下选项:​​stubsMode​​​​StubRunnerProperties.StubsMode​

  • ​StubRunnerProperties.StubsMode.CLASSPATH​​(默认值):从类路径中选取存根
  • ​StubRunnerProperties.StubsMode.LOCAL​​:从本地存储中选取存根(例如,.m2)
  • ​StubRunnerProperties.StubsMode.REMOTE​​:从远程位置选取存根

以下示例从本地位置选取存根:

@AutoConfigureStubRunner(repositoryRoot="https://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095", stubsMode = StubRunnerProperties.StubsMode.LOCAL)
类路径扫描

如果将属性设置为(或不设置默认值),则会扫描类路径。 请考虑以下示例:​​stubsMode​​​​StubRunnerProperties.StubsMode.CLASSPATH​​​​CLASSPATH​

@AutoConfigureStubRunner(ids = {
"com.example:beer-api-producer:+:stubs:8095",
"com.example.foo:bar:1.0.0:superstubs:8096"
})

可以将依赖项添加到类路径中,如下所示:

<dependency>
<groupId>com.example</groupId>
<artifactId>beer-api-producer-restdocs</artifactId>
<classifier>stubs</classifier>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.example.thing1</groupId>
<artifactId>thing2</artifactId>
<classifier>superstubs</classifier>
<version>1.0.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>

然后扫描类路径上的指定位置。为 扫描以下位置:​​com.example:beer-api-producer-restdocs​

  • /META-INF/com.example/beer-api-producer-restdocs/*/.*
  • /contracts/com.example/beer-api-producer-restdocs/*/.*
  • /mappings/com.example/beer-api-producer-restdocs/*/.*

为此,将扫描以下位置:​​com.example.thing1:thing2​

  • /META-INF/com.example.thing1/thing2/*/.*
  • /contracts/com.example.thing1/thing2/*/.*
  • /mappings/com.example.thing1/thing2/*/.*


打包 生产者存根。

为了实现适当的存根包装,生产商将按如下方式设置合同:

└── src
└── test
└── resources
└── contracts
└── com.example
└── beer-api-producer-restdocs
└── nested
└── contract3.groovy

通过使用Maven程序集插件或Gradle Jar任务,您必须创建以下内容 存根罐中的结构:

└── META-INF
└── com.example
└── beer-api-producer-restdocs
└── 2.0.0
├── contracts
│ └── nested
│ └── contract2.groovy
└── mappings
└── mapping.json

通过维护此结构,可以扫描类路径,您可以从消息传递或 无需下载工件的 HTTP 存根。

配置 HTTP 服务器存根

存根运行器有一个抽象底层的概念 HTTP 服务器的具体实现(例如,WireMock 是实现之一)。 有时,您需要对存根服务器执行一些额外的调整(这对于给定的实现是具体的)。 为此,存根运行器为您提供 批注中可用的属性和 JUnit 规则,可通过系统属性访问,您可以在其中提供 您的接口实现。实现可以改变 给定 HTTP 服务器存根的配置文件。​​HttpServerStub​​​​httpServerStubConfigurer​​​​org.springframework.cloud.contract.stubrunner.HttpServerStubConfigurer​

Spring Cloud 合约存根运行程序附带一个实现,您 可以扩展为 WireMock:。 在方法中, 您可以为给定存根提供自己的自定义配置。用途 案例可能是在HTTPS端口上为给定的项目ID启动WireMock。以下 示例显示了如何执行此操作:​​org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStubConfigurer​​​​configure​

例 1.WireMockHttpServerStubConfigurer 实现

@CompileStatic
static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer {

private static final Log log = LogFactory.getLog(HttpsForFraudDetection)

@Override
WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") {
int httpsPort = SocketUtils.findAvailableTcpPort()
log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server")
return httpStubConfiguration
.httpsPort(httpsPort)
}
return httpStubConfiguration
}
}

然后,您可以将其与注释重用,如下所示:​​@AutoConfigureStubRunner​

@AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/",
httpServerStubConfigurer = HttpsForFraudDetection)

每当找到 HTTPS 端口时,它都会优先于 HTTP 端口。

5.3.2. 运行存根

本节介绍如何运行存根。它包含以下主题:

  • HTTP 存根
  • 查看已注册的映射
  • 消息传递存根
HTTP 存根

存根在 JSON 文档中定义,其语法在WireMock 文档中定义。

以下示例在 JSON 中定义存根:

{
"request": {
"method": "GET",
"url": "/ping"
},
"response": {
"status": 200,
"body": "pong",
"headers": {
"Content-Type": "text/plain"
}
}
}
查看已注册的映射

每个存根协作者都会在端点下公开一个已定义映射的列表。​​__/admin/​

还可以使用该属性将映射转储到文件。 对于基于注释的方法,它将类似于以下示例:​​mappingsOutputFolder​

@AutoConfigureStubRunner(ids="a.b.c:loanIssuance,a.b.c:fraudDetectionServer",
mappingsOutputFolder = "target/outputmappings/")

对于 JUnit 方法,它类似于以下示例:

@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule()
.repoRoot("https://some_url")
.downloadStub("a.b.c", "loanIssuance")
.downloadStub("a.b.c:fraudDetectionServer")
.withMappingsOutputFolder("target/outputmappings")

然后,如果您签出文件夹,您将看到以下结构;​​target/outputmappings​

.
├── fraudDetectionServer_13705
└── loanIssuance_12255

这意味着有两个存根注册。如果我们看一下其中一个文件,我们会看到(对于WireMock) 可用于给定服务器的映射:​​fraudDetectionServer​​​​13705​​​​loanIssuance​​​​12255​

[{
"id" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7",
"request" : {
"url" : "/name",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "fraudDetectionServer"
},
"uuid" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7"
},
...
]
消息传递存根

根据提供的存根运行程序依赖项和 DSL,将自动设置消息传递路由。

5.4. 存根运行器 JUnit 规则和存根运行器 JUnit5 扩展

存根运行器附带一个 JUnit 规则,可让您下载和运行给定的存根 组和项目 ID,如以下示例所示:

@ClassRule
public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot())
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");

@BeforeClass
@AfterClass
public static void setupProps() {
System.clearProperty("stubrunner.repository.root");
System.clearProperty("stubrunner.classifier");
}

Ais 也可用于 JUnit 5.和工作,以非常相似的方式。在规则或扩展名之后 调用,存根运行器连接到您的 Maven 存储库,对于给定的列表 依赖项,尝试:​​StubRunnerExtension​​​​StubRunnerRule​​​​StubRunnerExtension​

  • 下载它们
  • 在本地缓存它们
  • 将它们解压缩到临时文件夹
  • 在提供的随机端口上为每个Maven依赖项启动WireMock服务器 端口范围或提供的端口
  • 向 WireMock 服务器提供所有有效的 WireMock 定义的 JSON 文件
  • 发送消息(记得传递接口的实现)MessageVerifier

Stub Runner 使用Eclipse Aether机制来下载 Maven 依赖项。 查看他们的文档以获取更多信息。

自从实施以来,他们让 您将找到已启动的存根,如以下示例所示:​​StubRunnerRule​​​​StubRunnerExtension​​​​StubFinder​

package org.springframework.cloud.contract.stubrunner;

import java.net.URL;
import java.util.Collection;
import java.util.Map;

import org.springframework.cloud.contract.spec.Contract;

/**
* Contract for finding registered stubs.
*
* @author Marcin Grzejszczak
*/
public interface StubFinder extends StubTrigger {

/**
* For the given groupId and artifactId tries to find the matching URL of the running
* stub.
* @param groupId - might be null. In that case a search only via artifactId takes
* place
* @param artifactId - artifact id of the stub
* @return URL of a running stub or throws exception if not found
* @throws StubNotFoundException in case of not finding a stub
*/
URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException;

/**
* For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]}
* tries to find the matching URL of the running stub. You can also pass only
* {@code artifactId}.
* @param ivyNotation - Ivy representation of the Maven artifact
* @return URL of a running stub or throws exception if not found
* @throws StubNotFoundException in case of not finding a stub
*/
URL findStubUrl(String ivyNotation) throws StubNotFoundException;

/**
* @return all running stubs
*/
RunningStubs findAllRunningStubs();

/**
* @return the list of Contracts
*/
Map<StubConfiguration, Collection<Contract>> getContracts();

}

以下示例提供了有关使用存根运行程序的更多详细信息:

@ClassRule
@Shared
StubRunnerRule rule = new StubRunnerRule()
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.repoRoot(StubRunnerRuleSpec.getResource("/m2repo/repository").toURI().toString())
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
.withMappingsOutputFolder("target/outputmappingsforrule")


def 'should start WireMock servers'() {
expect: 'WireMocks are running'
rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
rule.findStubUrl('loanIssuance') != null
rule.findStubUrl('loanIssuance') == rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
rule.findAllRunningStubs().isPresent('loanIssuance')
rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
"${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
}

def 'should output mappings to output folder'() {
when:
def url = rule.findStubUrl('fraudDetectionServer')
then:
new File("target/outputmappingsforrule", "fraudDetectionServer_${url.port}").exists()
}

请参阅JUnit 和 Spring 的通用属性以获取有关以下内容的更多信息 如何应用存根运行程序的全局配置。

要将 JUnit 规则或 JUnit 5 扩展与消息传递一起使用,您必须向规则生成器提供接口的实现(例如,)。 如果不这样做,则每当您尝试发送消息时,都会引发异常。​​MessageVerifier​​​​rule.messageVerifier(new MyMessageVerifier())​

5.4.1. Maven 设置

存根下载器遵循不同本地存储库文件夹的 Maven 设置。 存储库和配置文件的身份验证详细信息目前未考虑在内, 因此,您需要使用上述属性来指定它。

5.4.2. 提供固定端口

您还可以在固定端口上运行存根。您可以通过两种不同的方式执行此操作。 一种是在属性中传递它,另一种是使用 JUnit 规则。

5.4.3. 流畅的接口

使用理论时,可以添加存根下载 然后传递上次下载的存根的端口。以下示例演示如何执行此操作:​​StubRunnerRule​​​​StubRunnerExtension​

@ClassRule
public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot())
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance").withPort(12345)
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:12346");

@BeforeClass
@AfterClass
public static void setupProps() {
System.clearProperty("stubrunner.repository.root");
System.clearProperty("stubrunner.classifier");
}

对于前面的示例,以下测试有效:

then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL());
then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL());

5.4.4. 带弹簧的短截流道

带弹簧的存根运行器设置了存根运行程序项目的弹簧配置。

通过在配置文件中提供存根列表,存根运行器会自动下载 并在 WireMock 中注册选定的存根。

如果要查找存根依赖项的 URL,可以自动连线接口并使用 其方法如下:​​StubFinder​

@SpringBootTest(classes = Config, properties = [" stubrunner.cloud.enabled=false",
'foo=${stubrunner.runningstubs.fraudDetectionServer.port}',
'fooWithGroup=${stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port}'])
@AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/",
httpServerStubConfigurer = HttpsForFraudDetection)
@ActiveProfiles("test")
class StubRunnerConfigurationSpec extends Specification {

@Autowired
StubFinder stubFinder
@Autowired
Environment environment
@StubRunnerPort("fraudDetectionServer")
int fraudDetectionServerPort
@StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
int fraudDetectionServerPortWithGroupId
@Value('${foo}')
Integer foo

void setupSpec() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
WireMockHttpServerStubAccessor.clear()
}

void cleanupSpec() {
setupSpec()
}

def 'should mark all ports as random'() {
expect:
WireMockHttpServerStubAccessor.everyPortRandom()
}

def 'should start WireMock servers'() {
expect: 'WireMocks are running'
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
stubFinder.findStubUrl('loanIssuance') != null
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance')
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs')
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
stubFinder.findAllRunningStubs().isPresent('loanIssuance')
stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Fraud Detection is an HTTPS endpoint'
stubFinder.findStubUrl('fraudDetectionServer').toString().startsWith("https")
}

def 'should throw an exception when stub is not found'() {
when:
stubFinder.findStubUrl('nonExistingService')
then:
thrown(StubNotFoundException)
when:
stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId')
then:
thrown(StubNotFoundException)
}

def 'should register started servers as environment variables'() {
expect:
environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null
stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer)
and:
environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer)
and:
environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port") as Integer)
}

def 'should be able to interpolate a running stub in the passed test property'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
fraudPort > 0
environment.getProperty("foo", Integer) == fraudPort
environment.getProperty("fooWithGroup", Integer) == fraudPort
foo == fraudPort
}

@Issue("#573")
def 'should be able to retrieve the port of a running stub via an annotation'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
fraudPort > 0
fraudDetectionServerPort == fraudPort
fraudDetectionServerPortWithGroupId == fraudPort
}

def 'should dump all mappings to a file'() {
when:
def url = stubFinder.findStubUrl("fraudDetectionServer")
then:
new File("target/outputmappings/", "fraudDetectionServer_${url.port}").exists()
}

@Configuration
@EnableAutoConfiguration
static class Config {}

@CompileStatic
static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer {

private static final Log log = LogFactory.getLog(HttpsForFraudDetection)

@Override
WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") {
int httpsPort = SocketUtils.findAvailableTcpPort()
log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server")
return httpStubConfiguration
.httpsPort(httpsPort)
}
return httpStubConfiguration
}
}
}

这样做取决于以下配置文件:

stubrunner:
repositoryRoot: classpath:m2repo/repository/
ids:
- org.springframework.cloud.contract.verifier.stubs:loanIssuance
- org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer
- org.springframework.cloud.contract.verifier.stubs:bootService
stubs-mode: remote

除了使用属性,您还可以使用其中的属性。 下面的示例通过在批注上设置值来实现相同的结果:​​@AutoConfigureStubRunner​

@AutoConfigureStubRunner(
ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"],
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "classpath:m2repo/repository/")

存根流道弹簧按以下方式注册环境变量 对于每个已注册的 WireMock 服务器。以下示例显示了以下对象的存根运行程序 ID:​​com.example:thing1​​​​com.example:thing2​

  • ​stubrunner.runningstubs.thing1.port​
  • ​stubrunner.runningstubs.com.example.thing1.port​
  • ​stubrunner.runningstubs.thing2.port​
  • ​stubrunner.runningstubs.com.example.thing2.port​

可以在代码中引用这些值。

您还可以使用注释来注入正在运行的存根的端口。 注释的值只能是理论或。 以下示例工作显示了存根运行程序 ID。​​@StubRunnerPort​​​​groupid:artifactid​​​​artifactid​​​​com.example:thing1​​​​com.example:thing2​

@StubRunnerPort("thing1")
int thing1Port;
@StubRunnerPort("com.example:thing2")
int thing2Port;