91. Spring Cloud Contract 存根转轮

使用 Spring Cloud Contract Verifier 时可能遇到的问题之一是将生成的 WireMock JSON 存根从服务器端传递到 Client 端(或传递到各种 Client 端)。在 Client 端的消息传递方面也发生了同样的事情。

复制 JSON 文件并设置 Client 端手动进行消息传递是不可能的。这就是为什么我们引入了 Spring Cloud Contract Stub Runner。它可以自动为您下载并运行存根。

91.1 快照版本

将其他快照存储库添加到您的build.gradle文件以使用快照版本,快照版本在每次成功构建后都会自动上载:

Maven.

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

Gradle.

buildscript {
	repositories {
		mavenCentral()
		mavenLocal()
		maven { url "http://repo.spring.io/snapshot" }
		maven { url "http://repo.spring.io/milestone" }
		maven { url "http://repo.spring.io/release" }
	}

91.2 将存根发布为 JAR

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

Tip

对于 Maven 和 Gradle,该设置即可使用。但是,您可以根据需要自定义它。

Maven.

<!-- 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>
					$../../../../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 http://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>$../../../../src/test/resources/contracts</directory>
			<outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory>
			<includes>
				<include>**/*.groovy</include>
			</includes>
		</fileSet>
	</fileSets>
</assembly>

Gradle.

ext {
	contractsDir = file("mappings")
	stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
}

// Automatically added by plugin:
// copyContracts - copies contracts to the output folder from which JAR will be created
// verifierStubsJar - JAR with a provided stub suffix
// the presented publication is also added by the plugin but you can modify it as you wish

publishing {
	publications {
		stubs(MavenPublication) {
			artifactId "${project.name}-stubs"
			artifact verifierStubsJar
		}
	}
}

91.3 Stub Runner 核心

为服务合作者运行存根。将存根视为服务 Contract 允许将 stub-runner 用作Consumer 驱动的 Contract的实现。

Stub Runner 允许您自动下载提供的依赖项的存根(或从 Classpath 中选择存根),为其启动 WireMock 服务器,并使用正确的存根定义来提供它们。对于消息传递,定义了特殊的存根路由。

91.3.1 检索存根

您可以选择以下获取存根的选项

  • 基于醚的解决方案,可从 Artifactory/Nexus 下载带有存根的 JAR

  • Classpath 扫描解决方案,可通过模式搜索 Classpath 以检索存根

  • 编写自己的org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder实现以进行完全自定义

自定义存根转轮部分介绍了后一个示例。

Stub downloading

您可以通过stubsMode开关控制存根下载。它从StubRunnerProperties.StubsMode枚举中选择值。您可以使用以下选项

  • StubRunnerProperties.StubsMode.CLASSPATH(默认值)-将从 Classpath 中选择存根

  • StubRunnerProperties.StubsMode.LOCAL-将从本地存储区(例如.m2)中选择存根

  • StubRunnerProperties.StubsMode.REMOTE-将从远程位置选择存根

Example:

@AutoConfigureStubRunner(repositoryRoot="http://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095", stubsMode = StubRunnerProperties.StubsMode.LOCAL)

Classpath scanning

如果将stubsMode属性设置为StubRunnerProperties.StubsMode.CLASSPATH(或因为CLASSPATH为默认值,则不设置任何内容),则将扫描 Classpath。让我们看下面的例子:

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

如果您已将依赖项添加到 Classpath 中

Maven.

<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.foo</groupId>
    <artifactId>bar</artifactId>
    <classifier>superstubs</classifier>
    <version>1.0.0</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>*</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Gradle.

testCompile("com.example:beer-api-producer-restdocs:0.0.1-SNAPSHOT:stubs") {
    transitive = false
}
testCompile("com.example.foo:bar:1.0.0:superstubs") {
    transitive = false
}

然后,将扫描您的 Classpath 上的以下位置。对于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.foo:bar

  • /META-INF/com.example.foo/bar/ / .

  • /contracts/com.example.foo/bar/ / .

  • /mappings/com.example.foo/bar/ / .

Tip

如您所见,打包生产者存根时必须显式提供组和工件 ID。

生产者将像这样设置 Contract:

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

要实现正确的存根包装。

或使用Maven 程序集插件Gradle Jar任务,您必须在存根 jar 中创建以下结构。

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

通过维护这种结构,可以扫描 Classpath,而无需下载工件即可从消息传递/ HTTP 存根中受益。

配置 HTTP 服务器存根

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

Spring Cloud Contract Stub Runner 附带了一个可以扩展的实现,适用于 WireMock-org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStubConfigurer。在configure方法中,您可以为给定的存根提供自己的自定义配置。用例可能是在 HTTPs 端口上为给定的工件 ID 启动 WireMock。例:

WireMockHttpServerStubConfigurer implementation.

@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
	}
}

然后,您可以通过 Comments 重用它

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

只要找到 https 端口,它将优先于 http 端口。

91.3.2 正在运行的存根

使用主应用程序运行

您可以为主类设置以下选项:

-c, --classifier                Suffix for the jar containing stubs (e.
                                  g. 'stubs' if the stub jar would
                                  have a 'stubs' classifier for stubs:
                                  foobar-stubs ). Defaults to 'stubs'
                                  (default: stubs)
--maxPort, --maxp <Integer>     Maximum port value to be assigned to
                                  the WireMock instance. Defaults to
                                  15000 (default: 15000)
--minPort, --minp <Integer>     Minimum port value to be assigned to
                                  the WireMock instance. Defaults to
                                  10000 (default: 10000)
-p, --password                  Password to user when connecting to
                                  repository
--phost, --proxyHost            Proxy host to use for repository
                                  requests
--pport, --proxyPort [Integer]  Proxy port to use for repository
                                  requests
-r, --root                      Location of a Jar containing server
                                  where you keep your stubs (e.g. http:
                                  //nexus.
                                  net/content/repositories/repository)
-s, --stubs                     Comma separated list of Ivy
                                  representation of jars with stubs.
                                  Eg. groupid:artifactid1,groupid2:
                                  artifactid2:classifier
--sm, --stubsMode               Stubs mode to be used. Acceptable values
                                  [CLASSPATH, LOCAL, REMOTE]
-u, --username                  Username to user when connecting to
                                  repository

HTTP Stubs

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

Example:

{
    "request": {
        "method": "GET",
        "url": "/ping"
    },
    "response": {
        "status": 200,
        "body": "pong",
        "headers": {
            "Content-Type": "text/plain"
        }
    }
}

查看注册的 Map

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

您还可以使用mappingsOutputFolder属性将 Map 转储到文件。对于基于 Comments 的方法,它看起来像这样

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

对于这样的 JUnit 方法:

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

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

.
├── fraudDetectionServer_13705
└── loanIssuance_12255

这意味着注册了两个存根。 fraudDetectionServer在端口13705上注册,loanIssuance在端口12255上注册。如果我们看一下其中一个文件,我们将看到(对于 WireMock)可用于给定服务器的 Map:

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

Messaging Stubs

根据提供的 Stub Runner 依赖性和 DSL,将自动设置消息传递路由。

91.4 Stub Runner JUnit 规则和 Stub Runner JUnit5 扩展

Stub Runner 附带了 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");

JUnit 5 也有一个StubRunnerExtensionStubRunnerRuleStubRunnerExtension的工作方式非常相似。执行规则/扩展后,Stub Runner 连接到您的 Maven 存储库,并且对于给定的依赖项列表,尝试执行以下操作:

  • download them

  • 本地缓存它们

  • 将它们解压缩到一个临时文件夹

  • 从提供的端口范围/提供的端口为随机端口上的每个 Maven 依赖项启动 WireMock 服务器

  • 向 WireMock 服务器提供有效的 WireMock 定义的所有 JSON 文件

  • 也可以发送消息(记住要通过MessageVerifier接口的实现)

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

由于StubRunnerRuleStubRunnerExtension实现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;

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
	 * @return URL of a running stub or throws exception if not found
	 */
	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
	 */
	URL findStubUrl(String ivyNotation) throws StubNotFoundException;

	/**
	 * Returns all running stubs
	 */
	RunningStubs findAllRunningStubs();

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

}

Spock 测试中的用法示例:

@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 测试中的用法示例:

@Test
public void should_start_wiremock_servers() throws Exception {
	// expect: 'WireMocks are running'
		then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")).isNotNull();
		then(rule.findStubUrl("loanIssuance")).isNotNull();
		then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance"));
		then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull();
	// and:
		then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue();
		then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs", "fraudDetectionServer")).isTrue();
		then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isTrue();
	// and: 'Stubs were registered'
		then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance");
		then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer");
}

JUnit 5 扩展示例:

// Visible for Junit
@RegisterExtension
static StubRunnerExtension stubRunnerExtension = new StubRunnerExtension()
        .repoRoot(repoRoot())
        .stubsMode(StubRunnerProperties.StubsMode.REMOTE)
        .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
        .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
        .withMappingsOutputFolder("target/outputmappingsforrule");

@Test
void should_start_WireMock_servers() {
    assertThat(stubRunnerExtension.findStubUrl("org.springframework.cloud.contract.verifier.stubs",
            "loanIssuance")).isNotNull();
    assertThat(stubRunnerExtension.findStubUrl("loanIssuance")).isNotNull();
    assertThat(stubRunnerExtension.findStubUrl("loanIssuance")).isEqualTo(stubRunnerExtension
            .findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance"));
    assertThat(stubRunnerExtension
            .findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull();
}

检查 JUnit 和 Spring 的通用属性 ,以获取有关如何应用 Stub Runner 全局配置的更多信息。

Tip

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

91.4.1 Maven 设置

存根下载程序会使用其他本地存储库文件夹的 Maven 设置。当前不考虑存储库和配置文件的身份验证详细信息,因此您需要使用上述属性进行指定。

91.4.2 提供固定端口

您还可以在固定端口上运行存根。您可以通过两种不同的方式来实现。一种是在属性中传递它,另一种是通过 JUnit 规则的流畅 API。

91.4.3 Fluent API

使用StubRunnerRuleStubRunnerExtension时,您可以添加一个存根进行下载,然后将最后一个已下载存根的端口传递给该端口。

@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");

您可以看到对于此示例,以下测试有效:

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

91.4.4 Spring 的 Stub Runner

设置 Stub Runner 项目的 Spring 配置。

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

如果要查找存根依赖性的 URL,可以自动连接StubFinder接口并使用如下所示的方法:

@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(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

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

	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内的属性。您可以在下面找到通过在 Comments 上设置值来获得相同结果的示例。

@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/")

Stub Runner Spring 以下列方式为每个已注册的 WireMock 服务器注册环境变量。存根赛运行者 ID com.example:foocom.example:bar的示例。

  • stubrunner.runningstubs.foo.port

  • stubrunner.runningstubs.com.example.foo.port

  • stubrunner.runningstubs.bar.port

  • stubrunner.runningstubs.com.example.bar.port

您可以在代码中引用该代码。

您还可以使用@StubRunnerPortComments 注入正在运行的存根的端口。注解的值可以是groupid:artifactid或仅artifactid。存根赛运行者 ID com.example:foocom.example:bar的示例。

@StubRunnerPort("foo")
int fooPort;
@StubRunnerPort("com.example:bar")
int barPort;

91.5 Stub Runner Spring Cloud

Stub Runner 可以与 Spring Cloud 集成。

对于现实生活中的示例,您可以查看

91.5.1 存根服务发现

Stub Runner Spring Cloud的最重要 Feature 是存根

  • DiscoveryClient

  • Ribbon ServerList

这意味着无论您使用的是 Zookeeper,Consul,Eureka 还是其他工具,您都不需要在测试中使用它。我们将启动依赖项的 WireMock 实例,并告诉您的应用程序,无论何时您直接使用Feign,负载均衡RestTemplateDiscoveryClient来调用这些存根服务器,而不是调用 true 的服务发现工具。

例如,该测试将通过

def 'should make service discovery work'() {
	expect: 'WireMocks are running'
		"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
		"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
	and: 'Stubs can be reached via load service discovery'
		restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance'
		restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
}

对于以下配置文件

stubrunner:
  idsToServiceIds:
    ivyNotation: someValueInsideYourCode
    fraudDetectionServer: someNameThatShouldMapFraudDetectionServer

测试配置文件和服务发现

在集成测试中,您通常既不想调用发现服务(例如 Eureka)也不可以调用 Config Server。这就是为什么您要创建其他测试配置以禁用这些功能的原因。

由于spring-cloud-commons的某些限制,要实现此目的,您已通过如下所示的静态块(Eureka 的示例)禁用了这些属性

//Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156
    static {
        System.setProperty("eureka.client.enabled", "false");
        System.setProperty("spring.cloud.config.failFast", "false");
    }

91.5.2 附加配置

您可以使用stubrunner.idsToServiceIds:Map 将存根的 artifactId 与您的应用程序名称进行匹配。您可以通过提供:stubrunner.cloud.ribbon.enabled等于false来禁用 Stub Runner 功能区支持,可以通过提供:stubrunner.cloud.enabled等于false来禁用 Stub Runner 功能区支持

Tip

默认情况下,将对所有服务发现进行存根。这意味着无论您是否拥有DiscoveryClient,都将忽略其结果。但是,如果要重用它,只需将stubrunner.cloud.delegate.enabled设置为true,然后现有的DiscoveryClient结果将与存根的结果合并。

可以通过以下系统属性或环境变量来调整 Stub Runner 使用的默认 Maven 配置。

  • maven.repo.local-自定义 Maven 本地存储库位置的路径

  • org.apache.maven.user-settings-自定义 Maven 用户设置位置的路径

  • org.apache.maven.global-settings-Maven 全局设置位置的路径

91.6 Stub Runner 引导应用程序

Spring Cloud Contract Stub Runner Boot 是一个 Spring Boot 应用程序,它公开 REST 端点以触发消息传递标签并访问启动的 WireMock 服务器。

用例之一是在已部署的应用程序上运行一些冒烟(端到端)测试。您可以查看Spring 云管道项目以了解更多信息。

91.6.1 如何使用?

Stub Runner 服务器

只需添加

compile "org.springframework.cloud:spring-cloud-starter-stub-runner"

@EnableStubRunnerServerComments 一个类,构建一个 Fat JAR Jar,就可以开始了!

对于属性,请检查“ Stub Runner Spring”部分。

Stub Runner 服务器 Fat JAR

您可以从 Maven 下载独立的 JAR(例如对于版本 2.0.1.RELEASE),如下所示:

$ wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.0.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.0.1.RELEASE.jar'
$ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=...

Spring Cloud CLI

Spring Cloud CLI项目的1.4.0.RELEASE版本开始,您可以通过执行spring cloud stubrunner启动 Stub Runner Boot。

为了通过配置,只需在当前工作目录或名为config~/.spring-cloud的子目录中创建一个stubrunner.yml文件。该文件可能如下所示(运行本地安装的存根示例)

stubrunner.yml.

stubrunner:
  stubsMode: LOCAL
  ids:
    - com.example:beer-api-producer:+:9876

然后只需从终端窗口调用spring cloud stubrunner即可启动 Stub Runner 服务器。它将在端口8750上可用。

91.6.2 Endpoints

HTTP

  • GET /stubs-以ivy:integer格式返回所有正在运行的存根的列表

  • GET /stubs/{ivy}-返回给定ivy表示法的端口(调用端点ivy时也只能是artifactId)

Messaging

For Messaging

  • GET /triggers-以ivy : [ label1, label2 …]表示法返回所有运行标签的列表

  • POST /triggers/{label}-使用label执行触发器

  • POST /triggers/{ivy}/{label}-为给定的ivy符号使用label执行触发器(当调用端点ivy时也只能是artifactId)

91.6.3 Example

@ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader)
@SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec extends Specification {

	@Autowired StubRunning stubRunning

	def setup() {
		RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
				new TriggerController(stubRunning))
	}

	def 'should return a list of running stub servers in "full ivy:port" notation'() {
		when:
			String response = RestAssuredMockMvc.get('/stubs').body.asString()
		then:
			def root = new JsonSlurper().parseText(response)
			root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer
	}

	def 'should return a port on which a [#stubId] stub is running'() {
		when:
			def response = RestAssuredMockMvc.get("/stubs/${stubId}")
		then:
			response.statusCode == 200
			Integer.valueOf(response.body.asString()) > 0
		where:
			stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
					   'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
					   'org.springframework.cloud.contract.verifier.stubs:bootService:+',
					   'org.springframework.cloud.contract.verifier.stubs:bootService',
					   'bootService']
	}

	def 'should return 404 when missing stub was called'() {
		when:
			def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
		then:
			response.statusCode == 404
	}

	def 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
		when:
			String response = RestAssuredMockMvc.get('/triggers').body.asString()
		then:
			def root = new JsonSlurper().parseText(response)
			root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"])
	}

	def 'should trigger a messaging label'() {
		given:
			StubRunning stubRunning = Mock()
			RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
		when:
			def response = RestAssuredMockMvc.post("/triggers/delete_book")
		then:
			response.statusCode == 200
		and:
			1 * stubRunning.trigger('delete_book')
	}

	def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() {
		given:
			StubRunning stubRunning = Mock()
			RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
		when:
			def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book")
		then:
			response.statusCode == 200
		and:
			1 * stubRunning.trigger(stubId, 'delete_book')
		where:
			stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
	}

	def 'should throw exception when trigger is missing'() {
		when:
			RestAssuredMockMvc.post("/triggers/missing_label")
		then:
			Exception e = thrown(Exception)
			e.message.contains("Exception occurred while trying to return [missing_label] label.")
			e.message.contains("Available labels are")
			e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
			e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
	}

}

91.6.4 具有服务发现功能的 Stub Runner 引导

使用 Stub Runner Boot 的一种可能性是将其用作“烟雾测试”的存根的提要。这是什么意思?假设您不想将 50 个微服务部署到测试环境中以检查您的应用程序是否运行正常。您已经在构建过程中执行了一组测试,但是您还想确保应用程序的包装是正确的。您可以做的是将应用程序部署到环境中,启动它并在其上运行一些测试,以查看它是否正常运行。我们可以称这些测试为冒烟测试,因为它们的想法是仅检查少数几个测试场景。

这种方法的问题在于,如果您正在执行微服务,则很可能正在使用服务发现工具。 Stub Runner Boot 使您可以通过启动所需的 stub 并将其注册到服务发现工具中来解决此问题。让我们看一下使用 Eureka 进行此类设置的示例。假设 Eureka 已经在运行。

@SpringBootApplication
@EnableStubRunnerServer
@EnableEurekaClient
@AutoConfigureStubRunner
public class StubRunnerBootEurekaExample {

	public static void main(String[] args) {
		SpringApplication.run(StubRunnerBootEurekaExample.class, args);
	}

}

如您所见,我们想启动 Stub Runner 引导服务器@EnableStubRunnerServer,启用 EurekaClient 端@EnableEurekaClient,并且我们想打开@AutoConfigureStubRunner的 Stub Runner 功能。

现在,假设我们要启动此应用程序,以便存根自动注册。我们可以通过运行应用java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar来做到这一点,其中${SYSTEM_PROPS}将包含以下属性列表

-Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1)
-Dstubrunner.cloud.stubbed.discovery.enabled=false (2)
-Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService (3)
-Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer (4)

(1) - we tell Stub Runner where all the stubs reside
(2) - we don't want the default behaviour where the discovery service is stubbed. That's why the stub registration will be picked
(3) - we provide a list of stubs to download
(4) - we provide a list of artifactId to serviceId mapping

这样,您部署的应用程序可以通过服务发现将请求发送到启动的 WireMock 服务器。默认情况下,最可能在application.yml中设置了 1-3 点,因为它们不太可能更改。这样,您每次启动 Stub Runner Boot 时都只能提供要下载的存根列表。

每个 Consumer91.7 个存根

在某些情况下,同一终结点的 2 个使用者希望有 2 个不同的响应。

Tip

这种方法还使您可以立即知道哪个使用者正在使用 API 的哪个部分。您可以删除 API 产生的部分响应,并且可以看到哪些自动生成的测试失败。如果没有失败,那么您可以安全地删除响应的那部分,因为没有人使用它。

让我们看一下为生产者定义的名为producer的 Contract 的以下示例。有 2 个使用者:foo-consumerbar-consumer

Consumerfoo-service

request {
   url '/foo'
   method GET()
}
response {
    status OK()
    body(
       foo: "foo"
    }
}

Consumerbar-service

request {
   url '/foo'
   method GET()
}
response {
    status OK()
    body(
       bar: "bar"
    }
}

您不能为同一请求产生 2 个不同的响应。因此,您可以正确打包 Contract,然后从stubsPerConsumer功能中获利。

在生产者方面,Consumer 可以有一个文件夹,其中仅包含与他们相关的 Contract。通过将stubrunner.stubs-per-consumer标志设置为true,我们将不再注册所有存根,而仅注册与使用者应用程序名称相对应的存根。换句话说,我们将扫描每个存根的路径,如果它在路径中包含带有使用者名称的子文件夹,则它将被注册。

foo生产者一方,Contract 看起来像这样

.
└── contracts
    ├── bar-consumer
    │ ├── bookReturnedForBar.groovy
    │ └── shouldCallBar.groovy
    └── foo-consumer
        ├── bookReturnedForFoo.groovy
        └── shouldCallFoo.groovy

作为bar-consumer使用者,您可以将spring.application.namestubrunner.consumer-name设置为bar-consumer或按以下方式设置测试:

@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(properties = ["spring.application.name=bar-consumer"])
@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers",
		repositoryRoot = "classpath:m2repo/repository/",
		stubsMode = StubRunnerProperties.StubsMode.REMOTE,
		stubsPerConsumer = true)
class StubRunnerStubsPerConsumerSpec extends Specification {
...
}

然后,仅允许引用在名称中包含bar-consumer的路径下注册的存根(即来自src/test/resources/contracts/bar-consumer/some/contracts/…文件夹的存根)。

或明确设置 Consumer 名称

@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest
@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers",
		repositoryRoot = "classpath:m2repo/repository/",
		consumerName = "foo-consumer",
		stubsMode = StubRunnerProperties.StubsMode.REMOTE,
		stubsPerConsumer = true)
class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification {
...
}

然后,仅允许引用在名称中包含foo-consumer的路径下注册的存根(即来自src/test/resources/contracts/foo-consumer/some/contracts/…文件夹的存根)。

您可以查看issue 224以获得有关此更改背后原因的更多信息。

91.8 Common

本节简要介绍了常用属性,包括:

91.8.1 JUnit 和 Spring 的通用属性

您可以使用系统属性或 Spring 配置属性来设置重复属性。这是它们的名称及其默认值:

Property nameDefault valueDescription
stubrunner.minPort10000带存根的已启动 WireMock 的端口的最小值。
stubrunner.maxPort15000带存根的已启动 WireMock 的端口的最大值。
stubrunner.repositoryRoot Mavenrepo 网址。如果为空,则调用本地 Maven 存储库。
stubrunner.classifierstubs存根工件的默认分类器。
stubrunner.stubsModeCLASSPATH您想要获取和注册存根的方式
stubrunner.ids Ivy 表示法存根数组下载。
stubrunner.username 可选的用户名,用于访问使用存根存储 JAR 的工具。
stubrunner.password 可选密码,用于访问使用存根存储 JAR 的工具。
stubrunner.stubsPerConsumerfalse如果要为每个使用者使用不同的存根,而不是为每个使用者注册所有存根,则设置为true
stubrunner.consumerName 如果要为每个使用者使用一个存根并想覆盖使用者名称,只需更改此值即可。

91.8.2 存根运行器存根 ID

您可以提供存根以通过stubrunner.ids系统属性下载。他们遵循以下模式:

groupId:artifactId:version:classifier:port

请注意,versionclassifierport是可选的。

  • 如果您不提供port,则会随机选择一个。

  • 如果不提供classifier,则使用默认值。 (请注意,您可以通过以下方式传递空的分类器:groupId:artifactId:version:)。

  • 如果您不提供version,则将传递+并下载最新的version

port表示 WireMock 服务器的端口。

Tip

从 1.0.4 版开始,您可以提供希望 Stub Runner 考虑的一系列版本。您可以阅读有关以太版本版本在这里的更多信息。

91.9 Stub Runner Docker

我们正在发布spring-cloud/spring-cloud-contract-stub-runner Docker 映像,它将启动独立版本的 Stub Runner。

如果您想了解有关 Maven 的基础知识,工件 ID,组 ID,分类器和工件 Management 器的更多信息,请单击此处第 89.5 节“ Docker 项目”

91.9.1 如何使用

只需执行 docker 镜像即可。您可以将任何第 91.8.1 节“ JUnit 和 Spring 的通用属性”作为环境变量进行传递。惯例是所有字母都应大写。骆驼的大小写符号应该和点(.)通过下划线(_)分隔。例如。 stubrunner.repositoryRoot属性应表示为STUBRUNNER_REPOSITORY_ROOT环境变量。

91.9.2 非 JVM 项目中的 Client 端用法示例

我们想使用在此第 89.5.4 节“服务器端(nodejs)”步骤中创建的存根。假设我们要在端口9876上运行存根。 NodeJS 代码在这里可用:

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

让我们使用存根运行 Stub Runner Boot 应用程序。

# 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}"

发生的是

  • 一个独立的 Stub Runner 应用程序已启动

  • 它在端口9876上下载了坐标为com.example:bookstore:0.0.1.RELEASE:stubs的存根

  • 它是从http://192.168.0.100:8081/artifactory/libs-release-local运行的 Artifactory 下载的

  • 过一会儿,Stub Runner 将在端口8083上运行

  • 存根将在端口9876上运行

在服务器端,我们构建了一个有状态的存根。让我们使用 curl 声明存根已正确设置。

# let's execute 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

Tip

如果要使用在主机上本地构建的存根,则应传递环境变量-e STUBRUNNER_STUBS_MODE=LOCAL并安装本地 m2 -v "${HOME}/.m2/:/root/.m2:ro"的容量