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

Docker 项目

在本节中,我们将发布一个springcloud/spring-cloud-contractDocker 镜像 ,其中包含一个生成测试并在EXPLICIT模式 针对正在运行的应用程序。spring-doc.cadn.net.cn

EXPLICITmode 表示从 Contract 生成的测试发送 真实的请求,而不是嘲笑的请求。

我们还发布了一个spring-cloud/spring-cloud-contract-stub-runnerDocker 镜像 ,这将启动 Stub Runner 的独立版本。spring-doc.cadn.net.cn

Maven、JAR 和二进制存储简介

由于非 JVM 项目可以使用 Docker 镜像,因此最好 解释 Spring Cloud Contract 打包默认值背后的基本术语。spring-doc.cadn.net.cn

以下定义的一部分摘自 Maven 词汇表spring-doc.cadn.net.cn

  • Project:Maven 从项目的角度思考。项目 都是你构建的全部。这些项目遵循定义明确的 “项目对象模型”。项目可以依赖于其他项目 — 在这种情况下,后者称为 “依赖项”。一个项目可以 多个子项目的一致性。但是,这些子项目仍然是 平等地视为项目。spring-doc.cadn.net.cn

  • Artifact:工件是生产或使用的东西 按项目。Maven 为项目生成的工件示例 包括 JAR 文件以及源代码和二进制分发。每个工件 由组 ID 和工件 ID 唯一标识,该 ID 为 在组中唯一。spring-doc.cadn.net.cn

  • JAR:JAR 代表 Java ARchive。其格式基于 ZIP 文件格式。Spring Cloud Contract 将 Contract 打包并生成 stub 中的 stub 文件。spring-doc.cadn.net.cn

  • GroupId:组 ID 是项目的通用唯一标识符。 虽然这通常只是项目名称(例如commons-collections), 使用完全限定的包名称来区分它很有帮助 从具有相似名称的其他项目(例如org.apache.maven). 通常,当发布到 Artifact Manager 时,GroupId得到 斜杠分隔,并构成 URL 的一部分。例如,对于组 ID 为com.example工件 ID 为application,结果将是/com/example/application/.spring-doc.cadn.net.cn

  • Classifier:Maven 依赖关系表示法如下所示:groupId:artifactId:version:classifier.分类器是一个附加后缀 传递给依赖项 — 例如stubssources.相同的依赖项 (例如,com.example:application) 可以生成多个工件,这些工件 分类器彼此不同。spring-doc.cadn.net.cn

  • Artifact manager:生成二进制文件、源或包时,您将 喜欢它们以供其他人下载、引用或重复使用。在这种情况下 在 JVM 世界中,这些工件通常是 JAR。对于 Ruby 来说,这些工件就是宝石。 对于 Docker,这些工件是 Docker 镜像。您可以存储这些工件 在经理中。此类管理人的示例包括 ArtifactoryNexusspring-doc.cadn.net.cn

在生产者端生成测试

该图像在/contracts文件夹。 运行测试的输出在/spring-cloud-contract/build文件夹(用于调试 目的)。spring-doc.cadn.net.cn

您可以挂载 Contract 并传递环境变量。 然后,图像:spring-doc.cadn.net.cn

环境变量

Docker 镜像需要一些环境变量来指向 您正在运行的应用程序,到 Artifact Manager 实例,依此类推。 以下列表描述了环境变量:spring-doc.cadn.net.cn

表 1.Docker 环境变量

名字spring-doc.cadn.net.cn

描述spring-doc.cadn.net.cn

违约spring-doc.cadn.net.cn

ADDITIONAL_FLAGSspring-doc.cadn.net.cn

(仅限 Docker 映像)要传递给 Gradle 构建的其他标志spring-doc.cadn.net.cn

调试spring-doc.cadn.net.cn

(仅限 Docker 映像)适用于 Docker 映像 - 打开 Gradle 构建的调试模式spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_ARTIFACT_IDspring-doc.cadn.net.cn

包含合同的项目的工件 IDspring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_CLASSIFIERspring-doc.cadn.net.cn

具有 Contract 的项目的分类器spring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_GROUP_IDspring-doc.cadn.net.cn

包含合同的项目的组 IDspring-doc.cadn.net.cn

com.示例spring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_PATHspring-doc.cadn.net.cn

给定项目的合同路径,在具有 Contract 的项目内。默认为斜杠分隔EXTERNAL_CONTRACTS_GROUP_ID与 和 连接/EXTERNAL_CONTRACTS_ARTIFACT_ID. For example, for group id `cat-server-side.dog和工件 IDfish,将导致cat/dog/fish对于 contracts 路径。spring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_PASSWORDspring-doc.cadn.net.cn

(可选)Password (如果EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL需要身份验证。它默认为 'REPO_WITH_BINARIES_PASSWORD,如果未设置,则默认为 'passwordspring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URLspring-doc.cadn.net.cn

Artifact Manager 的 URL。它默认为REPO_WITH_BINARIES_URL环境变量,如果未设置,则默认为localhost:8081/artifactory/libs-release-localspring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_USERNAMEspring-doc.cadn.net.cn

(可选)用户名 (如果EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL需要身份验证。它默认为REPO_WITH_BINARIES_USERNAME.如果未设置,则默认为 'adminspring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_VERSIONspring-doc.cadn.net.cn

包含合同的项目版本。Defautls 相当于选择最新的spring-doc.cadn.net.cn

+spring-doc.cadn.net.cn

EXTERNAL_CONTRACTS_WORK_OFFLINEspring-doc.cadn.net.cn

如果设置为true中,从容器的.m2.挂载您的本地.m2作为容器的/root/.m2路径spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

FAIL_ON_NO_CONTRACTSspring-doc.cadn.net.cn

如果没有 Contract 存在,构建是否应该失败?spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

MESSAGING_TYPEspring-doc.cadn.net.cn

消息类型。可以是 [rabbit] 或 [kafka]。spring-doc.cadn.net.cn

PRODUCER_STUBS_CLASSIFIERspring-doc.cadn.net.cn

用于生成的生产者存根的存档分类器spring-doc.cadn.net.cn

存根spring-doc.cadn.net.cn

PROJECT_GROUPspring-doc.cadn.net.cn

项目的群组 IDspring-doc.cadn.net.cn

com.示例spring-doc.cadn.net.cn

PROJECT_NAMEspring-doc.cadn.net.cn

项目的工件 IDspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

PROJECT_VERSIONspring-doc.cadn.net.cn

您的项目版本spring-doc.cadn.net.cn

0.0.1-快照spring-doc.cadn.net.cn

PUBLISH_ARTIFACTSspring-doc.cadn.net.cn

如果设置为true,将构件发布到二进制存储spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

PUBLISH_ARTIFACTS_OFFLINEspring-doc.cadn.net.cn

如果设置为true,将对象发布到本地 m2spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

PUBLISH_STUBS_TO_SCMspring-doc.cadn.net.cn

如果设置为true将运行任务以将存根发布到 SCMspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

REPO_ALLOW_INSECURE_PROTOCOLspring-doc.cadn.net.cn

(可选)如果 <true>则允许通过不安全的 HTTP 将工件发布到 Artifact Managerspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

REPO_WITH_BINARIES_PASSWORDspring-doc.cadn.net.cn

(可选)保护 Artifact Manager 时的密码spring-doc.cadn.net.cn

密码spring-doc.cadn.net.cn

REPO_WITH_BINARIES_URLspring-doc.cadn.net.cn

Artifact Manager 的 URL(在本地运行时,默认为 Artifactory 的默认 URL)spring-doc.cadn.net.cn

本地主机:8081/artifactory/libs-release-localspring-doc.cadn.net.cn

REPO_WITH_BINARIES_USERNAMEspring-doc.cadn.net.cn

(可选)Artifact Manager 受到保护时的用户名spring-doc.cadn.net.cn

管理spring-doc.cadn.net.cn

STANDALONE_PROTOCOLspring-doc.cadn.net.cn

对于独立版本,应添加哪些附加协议spring-doc.cadn.net.cn

运行测试时使用以下环境变量:spring-doc.cadn.net.cn

表 2.Docker 环境变量 - 在运行时读取

名字spring-doc.cadn.net.cn

描述spring-doc.cadn.net.cn

违约spring-doc.cadn.net.cn

APPLICATION_BASE_URLspring-doc.cadn.net.cn

运行应用程序的 URL。spring-doc.cadn.net.cn

APPLICATION_PASSWORDspring-doc.cadn.net.cn

用于访问应用程序的可选密码。spring-doc.cadn.net.cn

APPLICATION_USERNAMEspring-doc.cadn.net.cn

用于访问应用程序的可选用户名。spring-doc.cadn.net.cn

MESSAGING_TRIGGER_CONNECT_TIMEOUTspring-doc.cadn.net.cn

连接到应用程序以触发消息的超时。spring-doc.cadn.net.cn

5000spring-doc.cadn.net.cn

MESSAGING_TRIGGER_READ_TIMEOUTspring-doc.cadn.net.cn

Timeout 从应用程序读取响应以触发消息。spring-doc.cadn.net.cn

5000spring-doc.cadn.net.cn

MESSAGING_TYPEspring-doc.cadn.net.cn

消息类型。可以是 [rabbit] 或 [kafka]。spring-doc.cadn.net.cn

MESSAGING_TYPEspring-doc.cadn.net.cn

定义处理基于消息的 Contract 时的消息类型。spring-doc.cadn.net.cn

SPRING_KAFKA_BOOTSTRAP_SERVERSspring-doc.cadn.net.cn

对于 Kafka - 代理地址。spring-doc.cadn.net.cn

SPRING_RABBITMQ_ADDRESSESspring-doc.cadn.net.cn

对于 RabbitMQ - 代理地址。spring-doc.cadn.net.cn

自定义 gradle 构建

您可以提供自定义的gradle.build要在运行容器时将自定义构建文件挂载为卷,即可在容器中运行:spring-doc.cadn.net.cn

$ docker run -v <absolute-path-of-your-custom-file>:/spring-cloud-contract/build.gradle springcloud/spring-cloud-contract:<version>

通过 HTTP 的使用示例

在本节中,我们将探讨一个简单的 MVC 应用程序。要开始使用,请克隆以下内容 git 存储库并 cd 到结果目录,方法是运行以下命令:spring-doc.cadn.net.cn

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

这些合同位于/contracts文件夹。spring-doc.cadn.net.cn

由于我们想要运行测试,因此可以运行以下命令:spring-doc.cadn.net.cn

$ npm test

但是,出于学习目的,我们将其拆分为多个部分,如下所示:spring-doc.cadn.net.cn

# Stop docker infra (nodejs, artifactory)
$ ./stop_infra.sh
# Start docker infra (nodejs, artifactory)
$ ./setup_infra.sh

# Kill & Run app
$ pkill -f "node app"
$ nohup node app &

# Prepare environment variables
$ SC_CONTRACT_DOCKER_VERSION="..."
$ APP_IP="192.168.0.100"
$ APP_PORT="3000"
$ ARTIFACTORY_PORT="8081"
$ APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
$ ARTIFACTORY_URL="http://${APP_IP}:${ARTIFACTORY_PORT}/artifactory/libs-release-local"
$ CURRENT_DIR="$( pwd )"
$ CURRENT_FOLDER_NAME=${PWD##*/}
$ PROJECT_VERSION="0.0.1.RELEASE"

# Run contract tests
$ docker run  --rm -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" -e "PUBLISH_ARTIFACTS=true" -e "PROJECT_NAME=${CURRENT_FOLDER_NAME}" -e "REPO_WITH_BINARIES_URL=${ARTIFACTORY_URL}" -e "PROJECT_VERSION=${PROJECT_VERSION}" -v "${CURRENT_DIR}/contracts/:/contracts:ro" -v "${CURRENT_DIR}/node_modules/spring-cloud-contract/output:/spring-cloud-contract-output/" springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"

# Kill app
$ pkill -f "node app"

通过 bash 脚本,将发生以下情况:spring-doc.cadn.net.cn

通过消息收发使用示例

如果您想通过 Docker 镜像(例如 如果是多语言应用程序),则必须满足以下先决条件:spring-doc.cadn.net.cn

消息传送协定示例

合约需要调用triggerMessage(…​)方法。该方法已在 docker 映像中所有测试的基类中提供,并将向生产者端的 HTTP 端点发送请求。您可以在下面找到此类合同的示例。spring-doc.cadn.net.cn

槽的
import org.springframework.cloud.contract.spec.Contract

Contract.make {
    description 'Send a pong message in response to a ping message'
    label 'ping_pong'
    input {
        // You have to provide the `triggerMessage` method with the `label`
        // as a String parameter of the method
        triggeredBy('triggerMessage("ping_pong")')
    }
    outputMessage {
        sentTo('output')
        body([
            message: 'pong'
        ])
    }
    metadata(
        [amqp:
         [
           outputMessage: [
               connectToBroker: [
                   declareQueueWithName: "queue"
               ],
                messageProperties: [
                    receivedRoutingKey: '#'
                ]
           ]
         ]
        ])
}
YAML
description: 'Send a pong message in response to a ping message'
label: 'ping_pong'
input:
    # You have to provide the `triggerMessage` method with the `label`
    # as a String parameter of the method
    triggeredBy: 'triggerMessage("ping_pong")'
outputMessage:
    sentTo: 'output'
    body:
        message: 'pong'
metadata:
    amqp:
        outputMessage:
            connectToBroker:
                declareQueueWithName: "queue"
            messageProperties:
                receivedRoutingKey: '#'

HTTP Endpoint to Trigger a Message

Why is there need to develop such an endpoint? Spring Cloud Contract would have to generate code in various languages (as it does in Java) to make it possible to trigger production code that sends a message to a broker. If such code is not generated then we need to be able to trigger the message anyways, and the way to do it is to provide an HTTP endpoint that the user will prepare in the language of their choosing.spring-doc.cadn.net.cn

The endpoint must have the following configuration:spring-doc.cadn.net.cn

Below you have an example of such an endpoint. If you’re interested in providing an example in your language don’t hesitate to file an issue in the Spring Cloud Contract repository at Github.spring-doc.cadn.net.cn

Python
#!/usr/bin/env python

from flask import Flask
from flask import jsonify
import pika
import os

app = Flask(__name__)

# Production code that sends a message to RabbitMQ
def send_message(cmd):
    connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    channel.basic_publish(
        exchange='output',
        routing_key='#',
        body=cmd,
        properties=pika.BasicProperties(
            delivery_mode=2,  # make message persistent
        ))
    connection.close()
    return " [x] Sent via Rabbit: %s" % cmd

# This should be ran in tests (shouldn't be publicly available)
if 'CONTRACT_TEST' in os.environ:
    @app.route('/springcloudcontract/<label>', methods=['POST'])
    def springcloudcontract(label):
        if label == "ping_pong":
            return send_message('{"message":"pong"}')
        else:
            raise ValueError('No such label expected.')

Running Message Tests on the Producer Side

Now, let’s generate tests from contracts to test the producer side. We will run bash code to start the Docker image with attached contracts, however we will also add variables for the messaging code to work. In this case let’s assume that the contracts are being stored in a Git repository.spring-doc.cadn.net.cn

#!/bin/bash
set -x

CURRENT_DIR="$( pwd )"

export SC_CONTRACT_DOCKER_VERSION="${SC_CONTRACT_DOCKER_VERSION:-4.0.1-SNAPSHOT}"
export APP_IP="$( ./whats_my_ip.sh )"
export APP_PORT="${APP_PORT:-8000}"
export APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
export PROJECT_GROUP="${PROJECT_GROUP:-group}"
export PROJECT_NAME="${PROJECT_NAME:-application}"
export PROJECT_VERSION="${PROJECT_VERSION:-0.0.1-SNAPSHOT}"
export PRODUCER_STUBS_CLASSIFIER="${PRODUCER_STUBS_CLASSIFIER:-stubs}"
export FAIL_ON_NO_CONTRACTS="${FAIL_ON_NO_CONTRACTS:-false}"
# In our Python app we want to enable the HTTP endpoint
export CONTRACT_TEST="true"
# In the Verifier docker container we want to add support for RabbitMQ
export MESSAGING_TYPE="rabbit"

# Let's start the infrastructure (e.g. via Docker Compose)
yes | docker-compose kill || echo "Nothing running"
docker-compose up -d

echo "SC Contract Version [${SC_CONTRACT_DOCKER_VERSION}]"
echo "Application URL [${APPLICATION_BASE_URL}]"
echo "Project Version [${PROJECT_VERSION}]"

# Let's run python app
gunicorn -w 4 --bind 0.0.0.0 main:app &
APP_PID=$!

# Generate and run tests
docker run  --rm \
                --name verifier \
                # For the image to find the RabbitMQ running in another container
                -e "SPRING_RABBITMQ_ADDRESSES=${APP_IP}:5672" \
                # We need to tell the container what messaging middleware we will use
                -e "MESSAGING_TYPE=${MESSAGING_TYPE}" \
                -e "PUBLISH_STUBS_TO_SCM=false" \
                -e "PUBLISH_ARTIFACTS=false" \
                -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" \
                -e "PROJECT_NAME=${PROJECT_NAME}" \
                -e "PROJECT_GROUP=${PROJECT_GROUP}" \
                -e "PROJECT_VERSION=${PROJECT_VERSION}" \
                -e "EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \
                -e "EXTERNAL_CONTRACTS_ARTIFACT_ID=${PROJECT_NAME}" \
                -e "EXTERNAL_CONTRACTS_GROUP_ID=${PROJECT_GROUP}" \
                -e "EXTERNAL_CONTRACTS_VERSION=${PROJECT_VERSION}" \
                -v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
                springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"

kill $APP_PID

yes | docker-compose kill

What will happen is:spring-doc.cadn.net.cn

  • Tests will be generated from contracts taken from Gitspring-doc.cadn.net.cn

  • In the contract we’ve provided an entry in metadata called declareQueueWithName that will lead to creation of a queue in RabbitMQ with the given name before the request to trigger the message is sentspring-doc.cadn.net.cn

  • Via the triggerMessage("ping_pong") method call a POST request to the Python application to the /springcloudcontract/ping_pong endpoint will be madespring-doc.cadn.net.cn

  • The Python application will generate and send a '{"message":"pong"}' JSON via RabbitMQ to an exchange called outputspring-doc.cadn.net.cn

  • The generated test will poll for a message sent to the output exchangespring-doc.cadn.net.cn

  • Once the message was received will assert its contentsspring-doc.cadn.net.cn

After the tests have passed we know that the message was properly sent from the Python app to RabbitMQ.spring-doc.cadn.net.cn

Running Stubs on the Consumer Side

This section describes how to use Docker on the consumer side to fetch and run stubs.spring-doc.cadn.net.cn

We publish a spring-cloud/spring-cloud-contract-stub-runner Docker image that starts the standalone version of Stub Runner.spring-doc.cadn.net.cn

Security

Since the Spring Cloud Contract Stub Runner Docker Image uses the standalone version of Stub Runner the same security considerations need to be taken. You can read more about those in this section of the documentation.spring-doc.cadn.net.cn

Environment Variables

You can run the docker image and pass any of the common properties for JUnit and Spring as environment variables. The convention is that all the letters should be upper case. The dot (.) should be replaced with underscore () characters. For example, the _stubrunner.repositoryRoot property should be represented as a STUBRUNNER_REPOSITORY_ROOT environment variable.spring-doc.cadn.net.cn

In addition to those variables you can set the following ones:spring-doc.cadn.net.cn

  • MESSAGING_TYPE - what type of messaging system are you using (currently supported are rabbit, kafka)spring-doc.cadn.net.cn

  • ADDITIONAL_OPTS - any additional properties that you would like to pass to the applicationspring-doc.cadn.net.cn

Example of Usage

We want to use the stubs created in this [docker-server-side] step. Assume that we want to run the stubs on port 9876. You can see the NodeJS code by cloning the repository and changing to the directory indicated in the following commands:spring-doc.cadn.net.cn

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

Now we can run the Stub Runner Boot application with the stubs, by running the following commands:spring-doc.cadn.net.cn

# Provide the Spring Cloud Contract Docker version
$ SC_CONTRACT_DOCKER_VERSION="..."
# The IP at which the app is running and Docker container can reach it
$ APP_IP="192.168.0.100"
# Spring Cloud Contract Stub Runner properties
$ STUBRUNNER_PORT="8083"
# Stub coordinates 'groupId:artifactId:version:classifier:port'
$ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876"
$ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local"
# Run the docker with Stub Runner Boot
$ docker run  --rm \
    -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" \
    -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" \
    -e "STUBRUNNER_STUBS_MODE=REMOTE" \
    -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" \
    -p "9876:9876" \
    springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"

When the preceding commands run,spring-doc.cadn.net.cn

On the server side, we built a stateful stub. We can use curl to assert that the stubs are setup properly. To do so, run the following commands:spring-doc.cadn.net.cn

# let's run the first request (no response is returned)
$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books
# Now time for the second request
$ curl -X GET http://localhost:9876/api/books
# You will receive contents of the JSON
If you want use the stubs that you have built locally, on your host, you should set the -e STUBRUNNER_STUBS_MODE=LOCAL environment variable and mount the volume of your local m2 (-v "${HOME}/.m2/:/home/scc/.m2:rw").

Example of Usage with Messaging

In order to make messaging work it’s enough to pass the MESSAGING_TYPE environment variable with kafka or rabbit values. This will lead to setting up the Stub Runner Boot Docker image with dependencies required to connect to the broker.spring-doc.cadn.net.cn

In order to set the connection properties you can check out Spring Cloud Stream properties page to set proper environment variables.spring-doc.cadn.net.cn

The most common property you would set is the location of the running middlewara. If a property to set it is called spring.rabbitmq.addresses or spring.kafka.bootstrap-servers then you should name the environment variable SPRING_RABBITMQ_ADDRESSES and SPRING_KAFKA_BOOTSTRAP_SERVERS respectively.spring-doc.cadn.net.cn

Running Contract Tests against Existing Middleware

There is legitimate reason to run your contract tests against existing middleware. Some testing frameworks might give you false positive results - the test within your build passes whereas on production the communication fails.spring-doc.cadn.net.cn

In Spring Cloud Contract docker images we give an option to connect to existing middleware. As presented in previous subsections we do support Kafka and RabbitMQ out of the box. However, via Apache Camel Components we can support other middleware too. Let’s take a look at the following examples of usage.spring-doc.cadn.net.cn

Spring Cloud Contract Docker and running Middleware

In order to connect to arbitrary middleware, we’ll leverage the standalone metadata entry in the contract section.spring-doc.cadn.net.cn

description: 'Send a pong message in response to a ping message'
label: 'standalone_ping_pong' (1)
input:
  triggeredBy: 'triggerMessage("ping_pong")' (2)
outputMessage:
  sentTo: 'rabbitmq:output' (3)
  body: (4)
    message: 'pong'
metadata:
  standalone: (5)
    setup: (6)
      options: rabbitmq:output?queue=output&routingKey=(7)
    outputMessage: (8)
      additionalOptions: routingKey=#&queue=output (9)
1 Label by which we’ll be able to trigger the message via Stub Runner
2 As in the previous messaging examples we’ll need to trigger the HTTP endpoint in the running application to make it send a message according to the provided protocol
3 protocol:destination as requested by Apache Camel
4 Output message body
5 Standalone metadata entry
6 Setup part will contain information about how to prepare for running contract tests before the actual call to HTTP endpoint of the running application is made
7 Apache Camel URI to be called in the setup phase. In this case we will try to poll for a message at the output exchange and due to to having the queue=output and routingKey= a queue with name output will be set and bound to the output exchange with routing key
8 Additional options (more technical ones) to be appended to the protocol:destination from point (3) - together will be combined in the following format rabbitmq:output?routingKey=#&queue=output.

For the contract tests to pass we will need as usual in case of messaging in polyglot environment a running application and running middleware. This time we will have different environment variables set for the Spring Cloud Contract Docker image.spring-doc.cadn.net.cn

#!/bin/bash
set -x

# Setup
# Run the middleware
docker-compose up -d rabbitmq (1)

# Run the python application
gunicorn -w 4 --bind 0.0.0.0 main:app & (2)
APP_PID=$!

docker run  --rm \
                --name verifier \
                -e "STANDALONE_PROTOCOL=rabbitmq" \ (3)
                -e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ (4)
                -e "PUBLISH_STUBS_TO_SCM=false" \
                -e "PUBLISH_ARTIFACTS=false" \
                -e "APPLICATION_BASE_URL=172.18.0.1" \
                -e "PROJECT_NAME=application" \
                -e "PROJECT_GROUP=group" \
                -e "EXTERNAL_CONTRACTS_ARTIFACT_ID=application" \
                -e "EXTERNAL_CONTRACTS_GROUP_ID=group" \
                -e "EXTERNAL_CONTRACTS_VERSION=0.0.1-SNAPSHOT" \
                -v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
                springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"


# Teardown
kill $APP_PID
yes | docker-compose kill
1 We need to have the middleware running first
2 The application needs to be up and running
3 Via the STANDALONE_PROTOCOL environment variable we will fetch a Apache Camel Component. The artifact that we will fetch is org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter. In other words STANDALONE_PROTOCOL is matching Camel’s component.
4 We’re setting addresses (we could be setting credentials) via Camel’s Spring Boot Starter mechanisms. Example for Apache Camel’s RabbitMQ Spring Boot Auto-Configuration

Stub Runner Docker and running Middleware

In order to trigger a stub message against running middleware, we can run Stub Runner Docker image in the following manner.spring-doc.cadn.net.cn

Example of usagespring-doc.cadn.net.cn

$ docker run \
    -e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ (1)
    -e "STUBRUNNER_IDS=group:application:0.0.1-SNAPSHOT" \ (2)
    -e "STUBRUNNER_REPOSITORY_ROOT=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \ (3)
    -e ADDITIONAL_OPTS="--thin.properties.dependencies.rabbitmq=org.apache.camel.springboot:camel-rabbitmq-starter:3.4.0" \ (4)
    -e "STUBRUNNER_STUBS_MODE=REMOTE" \ (5)
    -v "${HOME}/.m2/:/home/scc/.m2:rw" \ (6)
    -p 8750:8750 \ (7)
    springcloud/spring-cloud-contract-stub-runner:3.0.4-SNAPSHOT (8)
1 We’re injecting the address of RabbitMQ via Apache Camel’s Spring Boot Auto-Configuration
2 We’re telling Stub Runner which stubs to download
3 We’re providing an external location for our stubs (Git repository)
4 Via the ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION property we’re telling Stub Runner which additional dependency to fetch at runtime. In this case we want to fetch camel-rabbitmq-starter so XXX is a random string and we want to fetch org.apache.camel.springboot:camel-rabbitmq-starter artifact in version 3.4.0.
5 Since we’re using Git, the remote option of fetching stubs needs to be set
6 So that we speed up launching of Stub Runner, we’re attaching our local Maven repository .m2 as a volume. If you don’t have it populated you can consider setting the write permissions via :rw instead read only :ro.
7 We expose the port 8750 at which Stub Runner is running.
8 Coordinates of the Stub Runner Docker image.

After a while you’ll notice the following text in your console, which means that Stub Runner is ready to accept requests.spring-doc.cadn.net.cn

o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds
o.s.c.c.s.server.StubRunnerBoot          : Started StubRunnerBoot in 14.483 seconds (JVM running for 18.666)
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms

To get the list of triggers you can send an HTTP GET request to localhost:8750/triggers endpoint. To trigger a stub message, you can send a HTTP POST request to localhost:8750/triggers/standalone_ping_pong. In the console you’ll see:spring-doc.cadn.net.cn

o.s.c.c.v.m.camel.CamelStubMessages      : Will send a message to URI [rabbitmq:output?routingKey=#&queue=output]

If you check the RabbitMQ management console, you’ll see that there’s 1 message available in the output queue.spring-doc.cadn.net.cn