请使用 spring-cloud-contract 5.0.2 获取最新稳定版本!spring-doc.cadn.net.cn

使用 REST Docs

您可以使用 Spring REST Docs 为基于 Spring MockMvc、WebTestClient 或 RestAssured 的 HTTP API 生成文档(例如,以 AsciiDoc 格式)。在生成 API 文档的同时,您还可以通过使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,编写您的常规 REST Docs 测试用例,并使用 @AutoConfigureRestDocs 使得存根能够自动在 REST Docs 输出目录中生成。如下 UML 图展示了 REST Docs 的流程:spring-doc.cadn.net.cn

rest-docs

以下示例使用 MockMvcspring-doc.cadn.net.cn

@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 模拟桩。它匹配所有指向 /resource 路径的 GET 个请求。以下为使用 WebTestClient(用于测试 Spring WebFlux 应用程序)的相同示例:spring-doc.cadn.net.cn

@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"));
	}
}

在无需任何额外配置的情况下,这些测试会创建一个存根(stub),其中包含针对 HTTP 方法及所有头部的请求匹配器,但不包括 hostcontent-length。为了更精确地匹配请求(例如,匹配 POST 或 PUT 请求的正文内容),我们需要显式地创建一个请求匹配器。这样做会产生两个效果:spring-doc.cadn.net.cn

此功能的主要入口点是 WireMockRestDocs.verify(),它可作为 document() 便捷方法的替代品,如下例所示:spring-doc.cadn.net.cn

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 字段的有效 POST 请求都将收到本测试中定义的响应。您可以链式调用 .jsonPath() 来添加更多匹配器。如果对 JSON Path 不熟悉,JayWay 文档 可以帮助您快速上手。WebTestClient 版本的此测试具有类似的 verify() 静态辅助方法,您可在相同位置插入。spring-doc.cadn.net.cn

与其使用 jsonPathcontentType 这两个便捷方法,您还可以直接使用 WireMock API 来验证请求是否与所创建的存根匹配,如下例所示:spring-doc.cadn.net.cn

@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 路径来匹配请求头、查询参数和请求体。您可以利用这些功能创建支持更广泛参数的模拟响应。前面的例子生成了一个类似于以下示例的模拟响应:spring-doc.cadn.net.cn

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() 方法来创建请求匹配器,但不能同时使用这两种方法。

在消费者端,您可以将本节中先前生成的 resource.json 可以在类路径上使用(例如,通过 将存根发布为 JAR 文件)。之后,您可以通过多种方式创建一个使用 WireMock 的存根,包括如本文档前文所述,使用 @AutoConfigureWireMock(stubs="classpath:resource.json")spring-doc.cadn.net.cn

使用 REST Docs 生成契约

您还可以使用 Spring REST Docs 生成 Spring Cloud Contract DSL 文件和文档。如果结合 Spring Cloud WireMock 使用,您将同时获得契约(contracts)和桩(stubs)。spring-doc.cadn.net.cn

为什么您希望使用此功能?社区中的一些人就一种情况提出了问题:他们希望迁移到基于 DSL 的契约定义,但已拥有大量 Spring MVC 测试。使用此功能可生成契约文件,之后您可以对其进行修改并移至指定文件夹(在您的配置中定义),以便插件能够找到它们。spring-doc.cadn.net.cn

你可能会好奇,为什么此功能位于 WireMock 模块中。该功能存在于此,是因为生成契约和存根(stubs)是合乎逻辑的。

考虑以下测试:spring-doc.cadn.net.cn

		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))));

前面的测试创建了上一节中介绍的存根,生成了契约和一份文档文件。spring-doc.cadn.net.cn

该契约称为 index.groovy,其示例可能如下所示:spring-doc.cadn.net.cn

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.adocspring-doc.cadn.net.cn

指定优先级属性

方法 SpringCloudContractRestDocs.dslContract() 接受一个可选的 Map 参数,允许你在模板中指定额外的属性。spring-doc.cadn.net.cn

其中一个属性是 优先级 字段,您可以按如下方式指定:spring-doc.cadn.net.cn

SpringCloudContractRestDocs.dslContract(Map.of("priority", 1))

重写 DSL 合同模板

默认情况下,契约的输出基于名为 default-dsl-contract-only.snippet 的文件。spring-doc.cadn.net.cn

您可以通过重写 getTemplate() 方法来提供自定义模板文件,如下所示:spring-doc.cadn.net.cn

new ContractDslSnippet(){
    @Override
    protected String getTemplate() {
        return "custom-dsl-contract";
    }
}));

所以,上面的例子展示了这一行spring-doc.cadn.net.cn

.andDo(document("index", SpringCloudContractRestDocs.dslContract()));

应更改为:spring-doc.cadn.net.cn

.andDo(document("index", new ContractDslSnippet(){
                            @Override
                            protected String getTemplate() {
                                return "custom-dsl-template";
                            }
                        }));

模板通过在类路径中查找资源来解析。按以下顺序检查以下位置:spring-doc.cadn.net.cn

因此,在上述示例中,您应在 src/test/resources/org/springframework/restdocs/templates/custom-dsl-template.snippet 目录下放置一个名为 custom-dsl-template.snippet 的文件。spring-doc.cadn.net.cn