98. Spring Cloud Contract WireMock
Spring Cloud Contract WireMock 模块允许您在 Spring Boot application 中使用WireMock。查看samples了解更多详情。
如果你有 Spring Boot application 使用 Tomcat 作为嵌入式服务器(默认为spring-boot-starter-web
),你可以将spring-cloud-starter-contract-stub-runner
添加到 classpath 并在 order 中添加@AutoConfigureWireMock
以便能够在测试中使用 Wiremock。 Wiremock 作为存根服务器运行,您可以使用 Java API 或静态 JSON 声明来注册存根行为,作为测试的一部分。以下 code 显示了一个 example:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
public class WiremockForDocsTests {
// A service that calls out over HTTP
@Autowired private Service service;
// Using the WireMock APIs in the normal way:
@Test
public void contextLoads() throws Exception {
// Stubbing WireMock
stubFor(get(urlEqualTo("/resource"))
.willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!")));
// We're asserting if WireMock responded properly
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}
要在不同的 port 上启动存根服务器(对于 example),@AutoConfigureWireMock(port=9999)
。对于随机 port,请使用_val的 value。可以使用“wiremock.server.port”property 在 test application context 中绑定存根服务器 port。使用@AutoConfigureWireMock
将类型的 bean 添加到 test application context 中,它将被缓存在方法和具有相同 context 的 classes 之间,与 Spring integration 测试相同。
98.1 自动注册存根
如果使用@AutoConfigureWireMock
,它会从文件系统或 classpath(默认情况下,从file:src/test/resources/mappings
)注册 WireMock JSON 存根。您可以使用 annotation 中的stubs
属性自定义位置,该属性可以是 Ant-style 资源 pattern 或目录。如果是目录,则追加*/.json
。以下 code 显示了一个 example:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWireMock(stubs="classpath:/stubs")
public class WiremockImportApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}
实际上,WireMock 总是加载来自
src/test/resources/mappings
**的映射以及来自 stubs 属性中的自定义位置。要更改此行为,您还可以指定 files root,如本文档的下一部分所述。
98.2 使用 Files 指定存根体
WireMock 可以从 classpath 或文件系统上的 files 读取响应主体。在这种情况下,您可以在 JSON DSL 中看到响应具有bodyFileName
而不是(文字)body
。 files 是相对于根目录解析的(默认情况下为src/test/resources/__files
)。要自定义此位置,可以将@AutoConfigureWireMock
annotation 中的files
属性设置为 parent 目录的位置(换句话说,__files
是子目录)。您可以使用 Spring 资源表示法来引用file:…
或classpath:…
位置。不支持通用 URL。可以给出值列表,在这种情况下,WireMock 会在需要查找响应主体时解析存在的第一个文件。
配置
files
根时,它还会影响存根的自动加载,因为它们来自名为“mappings”的子目录中的根位置。files
的 value 对从stubs
属性显式加载的存根没有影响。
98.3 备选:使用 JUnit 规则
对于更传统的 WireMock 体验,您可以使用 JUnit @Rules
来启动和停止服务器。为此,请使用WireMockSpring
convenience class 获取Options
实例,如以下 example 所示:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class WiremockForDocsClassRuleTests {
// Start WireMock on some dynamic port
// for some reason `dynamicPort()` is not working properly
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(
WireMockSpring.options().dynamicPort());
// A service that calls out over HTTP to localhost:${wiremock.port}
@Autowired
private Service service;
// Using the WireMock APIs in the normal way:
@Test
public void contextLoads() throws Exception {
// Stubbing WireMock
wiremock.stubFor(get(urlEqualTo("/resource"))
.willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!")));
// We're asserting if WireMock responded properly
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}
@ClassRule
表示服务器在此 class 中的所有方法都已运行后关闭。
98.4 Rest Template 的轻松 SSL 验证
WireMock 允许您使用“https”URL 协议存根“安全”服务器。如果您的 application 想要在 integration 测试中联系该存根服务器,它将发现 SSL 证书无效(self-installed 证书的常见问题)。最好的选择通常是 re-configure client 使用“http”。如果这不是一个选项,您可以要求 Spring 配置一个忽略 SSL 验证错误的 HTTP client(当然,仅对测试这样做)。
为了最大限度地减少这项工作,您需要在应用程序中使用 Spring Boot RestTemplateBuilder
,如下面的示例所示:
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
您需要RestTemplateBuilder
,因为构建器通过回调来初始化它,因此可以在 client 中设置 SSL 验证。如果您使用@AutoConfigureWireMock
annotation 或存根运行器,则会在测试中自动执行此操作。如果使用 JUnit @Rule
方法,则还需要添加@AutoConfigureHttpClient
annotation,如下面的 example 所示:
@RunWith(SpringRunner.class)
@SpringBootTest("app.baseUrl=https://localhost:6443")
@AutoConfigureHttpClient
public class WiremockHttpsServerApplicationTests {
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(
WireMockSpring.options().httpsPort(6443));
...
}
如果您使用spring-boot-starter-test
,则 class 路径上有 Apache HTTP client,它由RestTemplateBuilder
选中并配置为忽略 SSL 错误。如果使用默认的java.net
client,则不需要 annotation(但不会造成任何伤害)。目前没有其他客户端的支持,但可能会在将来的版本中添加。
要禁用自定义RestTemplateBuilder
,请将wiremock.rest-template-ssl-enabled
property 设置为false
。
98.5 WireMock 和 Spring MVC Mocks
Spring Cloud Contract 提供了一个方便的 class,它可以将 JSON WireMock 存根加载到 Spring MockRestServiceServer
中。以下 code 显示了一个 example:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class WiremockForDocsMockServerApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
// will read stubs classpath
MockRestServiceServer server = WireMockRestServiceServer.with(this.restTemplate)
.baseUrl("http://example.org").stubs("classpath:/stubs/resource.json")
.build();
// We're asserting if WireMock responded properly
assertThat(this.service.go()).isEqualTo("Hello World");
server.verify();
}
}
baseUrl
value 前置于所有 mock calls,stubs()
方法将 stub 路径资源 pattern 作为参数。在前面的 example 中,/stubs/resource.json
中定义的存根被加载到 mock 服务器中。如果要求RestTemplate
访问http://example.org/
,则会在该 URL 处声明响应。可以指定多个 stub pattern,每个都可以是一个目录(对于所有“.json”的递归列表),一个固定的文件名(如上面的 example 中所示)或 Ant-style pattern。 JSON 格式是普通的 WireMock 格式,您可以在WireMock 网站中阅读。
目前,Spring Cloud Contract Verifier 支持 Tomcat,Jetty 和 Undertow 作为 Spring Boot 嵌入式服务器,而 Wiremock 本身对 Jetty(当前 9.2)的特定 version 具有“本机”支持。要使用本机 Jetty,您需要添加本机 Wiremock 依赖项并排除 Spring Boot 容器(如果有)。
98.6 自定义 WireMock configuration
您可以在 order 中注册org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer
类型的 bean 来自定义 WireMock configuration(e.g. 添加自定义变换器)。 例:
@Bean WireMockConfigurationCustomizer optionsCustomizer() {
return new WireMockConfigurationCustomizer() {
@Override public void customize(WireMockConfiguration options) {
// perform your customization here
}
};
}
98.7 使用 REST Docs 生成存根
Spring REST Docs可用于生成具有 Spring MockMvc 或WebTestClient
或 Rest Assured 的 HTTP API 的文档(对于 Asciidoctor 格式的 example)。在为 API 生成文档的同一时间,您还可以使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,编写正常的 REST Docs 测试用例并使用@AutoConfigureRestDocs
在 REST Docs 输出目录中自动生成存根。以下 code 显示使用MockMvc
的 example:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureRestDocs(outputDir = "target/snippets")
@AutoConfigureMockMvc
public class ApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void contextLoads() throws Exception {
mockMvc.perform(get("/resource"))
.andExpect(content().string("Hello World"))
.andDo(document("resource"));
}
}
此测试在“target/snippets/stubs/resource.json”处生成 WireMock 存根。它将所有 GET 请求与“/resource”路径匹配。与WebTestClient
(用于测试 Spring WebFlux applications)相同的 example 将如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureRestDocs(outputDir = "target/snippets")
@AutoConfigureWebTestClient
public class ApplicationTests {
@Autowired
private WebTestClient client;
@Test
public void contextLoads() throws Exception {
client.get().uri("/resource").exchange()
.expectBody(String.class).isEqualTo("Hello World")
.consumeWith(document("resource"));
}
}
在没有任何额外的 configuration 的情况下,这些测试会创建一个存根,其中包含 HTTP 方法的请求匹配器和除“host”和“content-length”之外的所有_header。要更精确地匹配请求(对于 example,要匹配 POST 或 PUT 的主体),我们需要显式创建请求匹配器。这样做有两个影响:
-
创建仅以您指定的方式匹配的存根。
-
断言测试用例中的请求也符合相同的条件。
此 feature 的主要入口点是WireMockRestDocs.verify()
,它可以用作document()
便捷方法的替代,如下面的示例所示:
import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureRestDocs(outputDir = "target/snippets")
@AutoConfigureMockMvc
public class ApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void contextLoads() throws Exception {
mockMvc.perform(post("/resource")
.content("{\"id\":\"123456\",\"message\":\"Hello World\"}"))
.andExpect(status().isOk())
.andDo(verify().jsonPath("$.id")
.stub("resource"));
}
}
此 contract 指定具有“id”字段的任何有效 POST 都接收此测试中定义的响应。您可以将 calls 链接到.jsonPath()
以添加其他匹配器。如果 JSON Path 不熟悉,JayWay 文档可以帮助您加快速度。这个测试的WebTestClient
version 有一个类似的verify()
静态帮助器,你可以在同一个地方插入它。
您还可以使用 WireMock API 来验证请求是否与创建的存根匹配,而不是jsonPath
和contentType
便捷方法,如以下 example 所示:
@Test
public void contextLoads() throws Exception {
mockMvc.perform(post("/resource")
.content("{\"id\":\"123456\",\"message\":\"Hello World\"}"))
.andExpect(status().isOk())
.andDo(verify()
.wiremock(WireMock.post(
urlPathEquals("/resource"))
.withRequestBody(matchingJsonPath("$.id"))
.stub("post-resource"));
}
WireMock API 非常丰富。您可以通过正则表达式和 JSON 路径匹配 headers,查询参数和请求正文。这些 features 可用于创建具有更广泛参数的存根。上面的 example 生成一个类似于以下示例的存根:
post-resource.json.
{
"request" : {
"url" : "/resource",
"method" : "POST",
"bodyPatterns" : [ {
"matchesJsonPath" : "$.id"
}]
},
"response" : {
"status" : 200,
"body" : "Hello World",
"headers" : {
"X-Application-Context" : "application:-1",
"Content-Type" : "text/plain"
}
}
}
您可以使用
wiremock()
方法或jsonPath()
和contentType()
方法来创建请求匹配器,但不能同时使用这两种方法。
在 consumer 端,您可以在 classpath 中使用本节前面生成的resource.json
(对于 example,使用<< publishing-stubs-as-jars]。之后,您可以使用 WireMock 以多种不同方式创建存根,包括使用@AutoConfigureWireMock(stubs="classpath:resource.json")
,如本文档前面所述。
98.8 使用 REST Docs 生成 Contracts
您还可以使用 Spring REST Docs 生成 Spring Cloud Contract DSL files 和文档。如果你与 Spring Cloud WireMock 结合使用,你会得到 contracts 和 stubs。
你为什么要使用这个 feature?社区中的一些人询问了他们想要转向 DSL-based contract 定义的情况,但他们已经有很多 Spring MVC 测试。使用此 feature 可以生成 contract files,稍后您可以修改这些文件并移动到文件夹(在 configuration 中定义),以便插件找到它们。
您可能想知道为什么这个功能在 WireMock 模块中。功能就在那里,因为生成 contracts 和 stubs 都是有意义的。
考虑以下测试:
this.mockMvc.perform(post("/foo")
.accept(MediaType.APPLICATION_PDF)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"foo\": 23, \"bar\" : \"baz\" }"))
.andExpect(status().isOk())
.andExpect(content().string("bar"))
// first WireMock
.andDo(WireMockRestDocs.verify()
.jsonPath("$[?(@.foo >= 20)]")
.jsonPath("$[?(@.bar in ['baz','bazz','bazzz'])]")
.contentType(MediaType.valueOf("application/json"))
.stub("shouldGrantABeerIfOldEnough"))
// then Contract DSL documentation
.andDo(document("index", SpringCloudContractRestDocs.dslContract()));
上述测试创建了上一节中显示的存根,生成了 contract 和文档文件。
contract 被称为index.groovy
,可能类似于以下 example:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method 'POST'
url '/foo'
body('''
{"foo": 23 }
''')
headers {
header('''Accept''', '''application/json''')
header('''Content-Type''', '''application/json''')
}
}
response {
status OK()
body('''
bar
''')
headers {
header('''Content-Type''', '''application/json;charset=UTF-8''')
header('''Content-Length''', '''3''')
}
testMatchers {
jsonPath('$[?(@.foo >= 20)]', byType())
}
}
}
生成的文档(在本例中为 Asciidoc 格式)包含格式化的 contract。该文件的位置为index/dsl-contract.adoc
。