Stub Runner 核心

Stub Runner 核心为服务协作方运行 Stub。将 Stub 视为服务契约,使你可以将 Stub Runner 用作消费者驱动契约的实现。

Stub Runner 允许你自动下载所提供依赖项的 Stub(或从 classpath 中选取),为它们启动 WireMock 服务器,并提供适当的 Stub 定义。对于消息传递,定义了特殊的 Stub 路由。

检索 Stub

你可以从以下选项中选择获取 Stub 的方式

  • 基于 Aether 的解决方案,从 Artifactory 或 Nexus 下载包含 Stub 的 JAR

  • Classpath 扫描解决方案,使用模式搜索 classpath 以检索 Stub

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

后一个示例在自定义 Stub Runner 部分有所描述。

下载 Stub

你可以使用 stubsMode 开关控制 Stub 的下载。它从 StubRunnerProperties.StubsMode 枚举中选取值。你可以使用以下选项

  • StubRunnerProperties.StubsMode.CLASSPATH(默认值):从 classpath 中选取 Stub

  • StubRunnerProperties.StubsMode.LOCAL:从本地存储(例如,.m2)中选取 Stub

  • StubRunnerProperties.StubsMode.REMOTE:从远程位置选取 Stub

以下示例从本地位置选取 Stub

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

Classpath 扫描

如果你将 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.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>
Gradle
testCompile("com.example:beer-api-producer-restdocs:0.0.1-SNAPSHOT:stubs") {
    transitive = false
}
testCompile("com.example.thing1:thing2: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.thing1:thing2,会扫描以下位置

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

  • /contracts/com.example.thing1/thing2/*/.*

  • /mappings/com.example.thing1/thing2/*/.*

打包生产者 Stub 时,必须明确提供 group 和 artifact ID。

为实现正确的 Stub 打包,生产者需要按如下方式设置契约

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

通过使用Maven assembly 插件Gradle Jar 任务,你必须在 Stub JAR 中创建以下结构

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

通过维护此结构,classpath 会被扫描,你可以直接使用消息传递或 HTTP Stub,无需下载 artifact。

配置 HTTP 服务器 Stub

Stub Runner 有一个 HttpServerStub 的概念,它抽象了底层具体的 HTTP 服务器实现(例如,WireMock 是其中一种实现)。有时,你需要对 Stub 服务器进行一些额外的调整(这取决于具体的实现)。为此,Stub Runner 提供了 httpServerStubConfigurer 属性,该属性在注解和 JUnit Rule 中可用,并且可以通过系统属性访问,你可以在其中提供 org.springframework.cloud.contract.stubrunner.HttpServerStubConfigurer 接口的实现。这些实现可以修改给定 HTTP 服务器 Stub 的配置文件。

Spring Cloud Contract Stub Runner 提供了一个你可以为 WireMock 扩展的实现:org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStubConfigurer。在 configure 方法中,你可以为给定 Stub 提供自己的自定义配置。用例可能是为给定 artifact ID 在 HTTPS 端口上启动 WireMock。以下示例展示了如何实现

示例 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 = TestSocketUtils.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 端口。

运行 Stub

本节描述如何运行 Stub。它包含以下主题

HTTP Stub

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

以下示例在 JSON 中定义了一个 Stub

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

查看已注册的映射

每个 Stub 协作方都在 __/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

这意味着注册了两个 Stub。fraudDetectionServer 注册在端口 13705loanIssuance 注册在端口 12255。如果我们查看其中一个文件,会看到(对于 WireMock)给定服务器可用的映射

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

消息传递 Stub

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