使用 REST 文档
您可以使用 Spring REST Docs 生成
文档(例如,Asciidoc 格式)用于使用 Spring MockMvc 的 HTTP API,
WebTestClient 或 RestAssured。在为 API 生成文档的同时,您还可以
使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,请将您的
普通 REST Docs 测试用例和使用@AutoConfigureRestDocs
要有存根
在 REST Docs 输出目录中自动生成。以下 UML 图显示了
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
.它匹配
都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 方法和除host
和content-length
.要匹配
更精确地请求(例如,为了匹配 POST 或 PUT 的正文),我们需要
显式创建请求匹配器。这样做有两个效果:
-
创建仅以您指定的方式匹配的存根。
-
断言测试用例中的请求也匹配相同的条件。
此功能的主要入口点是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"))
.andDo(document("resource"));
}
}
前面的合约指定任何具有id
字段接收响应
在此测试中定义。您可以将调用链接在一起.jsonPath()
添加其他
匹配器。如果 JSON 路径不熟悉,则 JayWay
文档可以帮助您快速上手。此测试的 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"))
.andDo(document("post-resource"))));
}
WireMock API 很丰富。您可以通过以下方式匹配标头、查询参数和请求正文 正则表达式以及 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() 方法来创建请求匹配器,但不能同时使用这两种方法。 |
在消费者方面,您可以将resource.json
在本节前面生成
在类路径上可用(例如,通过将存根发布为 JAR)。之后,您可以创建一个在
多种不同的方式,包括使用@AutoConfigureWireMock(stubs="classpath:resource.json")
,如本文前面所述
公文。
使用 REST 文档生成契约
您还可以使用 Spring REST 生成 Spring Cloud Contract DSL 文件和文档 文档。如果与 Spring Cloud WireMock 结合使用,则会同时获得两个合约 和存根。
为什么要使用此功能?社区有人提问 关于他们希望转向基于 DSL 的合同定义的情况, 但他们已经有很多 Spring MVC 测试。使用此功能可以生成 您稍后可以修改并移动到文件夹(在 configuration),以便插件找到它们。
您可能想知道为什么此功能位于 WireMock 模块中。功能 是因为同时生成合约和存根是有意义的。 |
考虑以下测试:
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")))
// then Contract DSL documentation
.andDo(document("index", SpringCloudContractRestDocs.dslContract(Maps.of("priority", 1))));
前面的测试创建了上一节中介绍的存根,并生成了 合同和文档文件。
合约称为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''')
}
bodyMatchers {
jsonPath('$[?(@.foo >= 20)]', byType())
}
}
}
生成的文档(在本例中为 Asciidoc 格式)包含格式化的
合同。此文件的位置为index/dsl-contract.adoc
.
指定优先级属性
方法SpringCloudContractRestDocs.dslContract()
采用可选的 Map 参数,允许您在模板中指定其他属性。
其中一个属性是您可以指定的优先级字段,如下所示:
SpringCloudContractRestDocs.dslContract(Map.of("priority", 1))
重写 DSL 协定模板
默认情况下,合约的输出基于名为default-dsl-contract-only.snippet
.
您可以通过覆盖 getTemplate() 方法来提供自定义模板文件,如下所示:
new ContractDslSnippet(){
@Override
protected String getTemplate() {
return "custom-dsl-contract";
}
}));
所以上面显示这行的示例
.andDo(document("index", SpringCloudContractRestDocs.dslContract()));
应更改为:
.andDo(document("index", new ContractDslSnippet(){
@Override
protected String getTemplate() {
return "custom-dsl-template";
}
}));
模板是通过在类路径上查找资源来解析的。按顺序检查以下位置:
-
org/springframework/restdocs/templates/${templateFormatId}/${name}.snippet
-
org/springframework/restdocs/templates/${name}.snippet
-
org/springframework/restdocs/templates/${templateFormatId}/default-${name}.snippet
因此,在上面的示例中,您应该将名为 custom-dsl-template.snippet 的文件放在src/test/resources/org/springframework/restdocs/templates/custom-dsl-template.snippet