96. Spring Cloud Contract WireMock
Spring Cloud Contract WireMock 模块使您可以在 Spring Boot 应用程序中使用WireMock。请查看samples以获取更多详细信息。
如果您有一个使用 Tomcat 作为嵌入式服务器的 Spring Boot 应用程序(默认值为spring-boot-starter-web
),则可以将spring-cloud-starter-contract-stub-runner
添加到 Classpath 中,并添加@AutoConfigureWireMock
以便能够在测试中使用 Wiremock。 Wiremock 作为存根服务器运行,您可以在测试中使用 Java API 或通过静态 JSON 声明来注册存根行为。以下代码显示了一个示例:
@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!");
}
}
要在其他端口上启动存根服务器,请使用@AutoConfigureWireMock(port=9999)
(例如)。对于随机端口,请使用0
的值。可以在测试应用程序上下文中使用“ wiremock.server.port”属性绑定存根服务器端口。使用@AutoConfigureWireMock
将类型WiremockConfiguration
的 bean 添加到测试应用程序上下文中,该 bean 将被缓存在具有相同上下文的方法和类之间,这与 Spring 集成测试相同。您也可以将WireMockServer
类型的 bean 注入测试中。
96.1 自动注册存根
如果使用@AutoConfigureWireMock
,它将从文件系统或 Classpath(默认情况下为file:src/test/resources/mappings
)注册 WireMock JSON 存根。您可以使用 Comments 中的stubs
属性来自定义位置,该属性可以是 Ant 样式的资源模式或目录。如果是目录,则附加*/.json
。以下代码显示了一个示例:
@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!");
}
}
Note
实际上,WireMock 始终从 stubs 属性中的src/test/resources/mappings
以及 自定义位置加载 Map。要更改此行为,还可以按照本文档下一节中的说明指定文件根。
如果您使用的是 Spring Cloud Contract 的默认存根 jar,则您的存根将存储在/META-INF/group-id/artifact-id/versions/mappings/
文件夹下。如果要从该位置,所有嵌入式 JAR 中注册所有存根,那么使用以下语法就足够了。
@AutoConfigureWireMock(port = 0, stubs = "classpath*:/META-INF/**/mappings/**/*.json")
96.2 使用文件指定存根实体
WireMock 可以从 Classpath 或文件系统上的文件中读取响应正文。在这种情况下,您可以在 JSON DSL 中看到响应具有bodyFileName
而不是(Literals)body
。相对于根目录(默认为src/test/resources/__files
)解析文件。要自定义此位置,可以将@AutoConfigureWireMock
注解中的files
属性设置为父目录的位置(换句话说,__files
是子目录)。您可以使用 Spring 资源表示法来引用file:…
或classpath:…
位置。不支持通用网址。可以给出一个值列表,在这种情况下,WireMock 会在需要查找响应正文时解析存在的第一个文件。
Note
当您配置files
根目录时,它也会影响存根的自动加载,因为它们来自子目录“Map”中的根目录。 files
的值对从stubs
属性显式加载的存根没有影响。
96.3 替代方法:使用 JUnit 规则
要获得更常规的 WireMock 体验,可以使用 JUnit @Rules
来启动和停止服务器。为此,请使用WireMockSpring
便利类获取Options
实例,如以下示例所示:
@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());
@Before
public void setup() {
this.service.setBase("http://localhost:" + wiremock.port());
}
// A service that calls out over HTTP to wiremock's 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
表示在运行了此类中的所有方法之后,服务器将关闭。
96.4 轻松 SSL 验证其余模板
WireMock 允许您使用“ https” URL 协议对“安全”服务器进行存根。如果您的应用程序希望在集成测试中联系该存根服务器,它将发现 SSL 证书无效(自安装证书的常见问题)。最好的选择通常是将 Client 端重新配置为使用“ http”。如果这不是一个选择,则可以要求 Spring 配置忽略 SSL 验证错误的 HTTPClient 端(当然,仅对测试而言如此)。
为了使此工作最小,您需要在应用程序中使用 Spring Boot RestTemplateBuilder
,如以下示例所示:
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
您需要RestTemplateBuilder
,因为构建器是通过回调传递的,以对其进行初始化,因此此时可以在 Client 端中设置 SSL 验证。如果您使用的是@AutoConfigureWireMock
注解或存根运行程序,则会在测试中自动发生。如果使用 JUnit @Rule
方法,则还需要添加@AutoConfigureHttpClient
Comments,如以下示例所示:
@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
,则在 Classpath 上有 Apache HTTPClient 端,并且由RestTemplateBuilder
选择它,并配置为忽略 SSL 错误。如果使用默认的java.net
Client 端,则不需要 Comments(但不会造成任何危害)。当前不支持其他 Client 端,但可能会在将来的版本中添加。
要禁用自定义RestTemplateBuilder
,请将wiremock.rest-template-ssl-enabled
属性设置为false
。
96.5 WireMock 和 Spring MVC 模拟
Spring Cloud Contract 提供了一个便利类,可以将 JSON WireMock 存根加载到 Spring MockRestServiceServer
中。以下代码显示了一个示例:
@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
值位于所有模拟调用之前,并且stubs()
方法采用存根路径资源模式作为参数。在前面的示例中,在/stubs/resource.json
处定义的存根被加载到模拟服务器中。如果要求RestTemplate
访问http://example.org/
,则它将获得该 URL 处声明的响应。可以指定多个存根模式,每个存根模式都可以是目录(用于所有“ .json”的递归列表),固定文件名(如上例所示)或 Ant 样式的模式。 JSON 格式是标准的 WireMock 格式,您可以在WireMock website中进行阅读。
当前,Spring Cloud Contract Verifier 支持将 Tomcat,Jetty 和 Undertow 作为 Spring Boot 嵌入式服务器,而 Wiremock 本身对特定版本的 Jetty(当前为 9.2)具有“本机”支持。要使用本地 Jetty,您需要添加本地 Wiremock 依赖项,并排除 Spring Boot 容器(如果有)。
96.6 自定义 WireMock 配置
您可以注册org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer
类型的 bean,以自定义 WireMock 配置(例如,添加自定义转换器)。例:
@Bean
WireMockConfigurationCustomizer optionsCustomizer() {
return new WireMockConfigurationCustomizer() {
@Override
public void customize(WireMockConfiguration options) {
// perform your customization here
}
};
}
96.7 使用 REST 文档生成存根
Spring REST 文件可用于为带有 Spring MockMvc 或WebTestClient
或 Rest Assured 的 HTTP API 生成文档(例如,Asciidoctor 格式)。在为 API 生成文档的同时,还可以使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,编写您的常规 REST Docs 测试用例,并使用@AutoConfigureRestDocs
可以在 REST Docs 输出目录中自动生成存根。以下代码显示了使用MockMvc
的示例:
@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 应用程序)如下所示:
@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"));
}
}
在没有任何其他配置的情况下,这些测试将创建一个带有 HTTP 方法的请求匹配器和所有 Headers(“主机”和“内容长度”除外)的存根。为了更精确地匹配请求(例如,匹配 POST 或 PUT 的正文),我们需要显式创建一个请求匹配器。这样做有两个效果:
-
创建仅以您指定的方式匹配的存根。
-
assert 测试用例中的请求也匹配相同的条件。
此功能的主要入口点是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 都会收到此测试中定义的响应。您可以将对.jsonPath()
的调用链接在一起,以添加其他匹配器。如果不熟悉 JSON 路径,则JayWay documentation可以帮助您快速Starter。此测试的WebTestClient
版本具有与您在同一位置插入的类似verify()
静态助手。
除了使用jsonPath
和contentType
便捷方法之外,您还可以使用 WireMock API 来验证请求是否与创建的存根匹配,如以下示例所示:
@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,查询参数和请求正文。这些功能可用于创建具有更广泛参数范围的存根。上面的示例生成一个类似于以下示例的存根:
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"
}
}
}
Note
您可以使用wiremock()
方法或jsonPath()
和contentType()
方法来创建请求匹配器,但是不能同时使用这两种方法。
在使用者方面,可以使本节前面生成的resource.json
在 Classpath 上可用(例如,通过<< publishing-stubs-as-jars)。之后,可以使用 WireMock 以多种不同方式创建存根,包括使用@AutoConfigureWireMock(stubs="classpath:resource.json")
,如本文档前面所述。
96.8 使用 REST 文档生成 Contract
您还可以使用 Spring REST Docs 生成 Spring Cloud Contract DSL 文件和文档。如果与 Spring Cloud WireMock 结合使用,则可以同时获取 Contract 和存根。
您为什么要使用此功能?社区中的一些人询问有关他们希望转向基于 DSL 的 Contract 定义的情况的问题,但是他们已经进行了许多 Spring MVC 测试。使用此功能,您可以生成 Contract 文件,以后可以修改 Contract 文件并将其移动到文件夹(在配置中定义),以便插件找到它们。
Tip
您可能想知道为什么 WireMock 模块中有此功能。之所以具有此功能,是因为生成 Contract 和存根都是有意义的。
考虑以下测试:
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
,可能类似于以下示例:
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
。