对于最新的稳定版本,请使用 spring-cloud-contract 4.3.0! |
使用 REST 文档
您可以使用 Spring REST Docs 为 HTTP API 生成文档(例如,以 Asciidoc 格式)与 Spring MockMvc、WebTestClient 或 RestAssured 一起生成。在为 API 生成文档的同时,您还可以使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,请编写normal 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)。之后,您可以创建一个使用 WireMock 的存根多种不同的方式,包括使用@AutoConfigureWireMock(stubs="classpath:resource.json")
,如本文前面所述 公文。
使用 REST 文档生成契约
您还可以使用 Spring REST 生成 Spring Cloud Contract DSL 文件和文档 文档。 如果您与 Spring Cloud WireMock 结合使用,您将获得合约和存根。
为什么要使用这个功能?社区中的一些人提出了问题关于他们想要迁移到基于 DSL 的合约定义的情况,但他们已经有很多 Spring MVC 测试。使用此功能可以让您生成您稍后可以修改并移动到文件夹(在配置中定义)的合约文件,以便插件找到它们。
您可能想知道为什么此功能位于 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