此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 spring-cloud-contract 4.2.0spring-doc.cadn.net.cn

使用 REST 文档

您可以使用 Spring REST Docs 生成 使用 Spring MockMvc 的 HTTP API 的文档(例如,以 Asciidoc 格式), WebTestClient 或 RestAssure 的 Recover。在为 API 生成文档的同时,您还可以 使用 Spring Cloud Contract WireMock 生成 WireMock 存根。为此,请编写 普通的 REST Docs 测试用例和使用@AutoConfigureRestDocs使存根为 在 REST Docs 输出目录中自动生成。下面的 UML 图显示了 REST 文档流程:spring-doc.cadn.net.cn

rest-docs

以下示例使用MockMvc:spring-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.它匹配 都GET请求发送到/resource路径。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"));
	}
}

无需任何其他配置,这些测试将创建一个带有请求匹配器的存根 对于 HTTP 方法和除hostcontent-length.要匹配 request 更精确地(例如,为了匹配 POST 或 PUT 的正文),我们需要 显式创建请求匹配器。这样做有两个效果:spring-doc.cadn.net.cn

此功能的主要入口点是WireMockRestDocs.verify(),可以使用 作为document()convenience 方法,如下所示 示例显示: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"));
	}
}

The preceding contract specifies that any valid POST with an id field receives the response defined in this test. You can chain together calls to .jsonPath() to add additional matchers. If JSON Path is unfamiliar, the JayWay documentation can help you get up to speed. The WebTestClient version of this test has a similar verify() static helper that you insert in the same place.spring-doc.cadn.net.cn

Instead of the jsonPath and contentType convenience methods, you can also use the WireMock APIs to verify that the request matches the created stub, as the following example shows: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"))));
}

The WireMock API is rich. You can match headers, query parameters, and the request body by regex as well as by JSON path. You can use these features to create stubs with a wider range of parameters. The preceding example generates a stub resembling the following example: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"
    }
  }
}
You can use either the wiremock() method or the jsonPath() and contentType() methods to create request matchers, but you cannot use both approaches.

On the consumer side, you can make the resource.json generated earlier in this section available on the classpath (by Publishing Stubs as JARs, for example). After that, you can create a stub that uses WireMock in a number of different ways, including by using @AutoConfigureWireMock(stubs="classpath:resource.json"), as described earlier in this document.spring-doc.cadn.net.cn

Generating Contracts with REST Docs

You can also generate Spring Cloud Contract DSL files and documentation with Spring REST Docs. If you do so in combination with Spring Cloud WireMock, you get both the contracts and the stubs.spring-doc.cadn.net.cn

Why would you want to use this feature? Some people in the community asked questions about a situation in which they would like to move to DSL-based contract definition, but they already have a lot of Spring MVC tests. Using this feature lets you generate the contract files that you can later modify and move to folders (defined in your configuration) so that the plugin finds them.spring-doc.cadn.net.cn

You might wonder why this functionality is in the WireMock module. The functionality is there because it makes sense to generate both the contracts and the stubs.

Consider the following test: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))));

The preceding test creates the stub presented in the previous section, generating both the contract and a documentation file.spring-doc.cadn.net.cn

The contract is called index.groovy and might resemble the following example: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())
        }
    }
}

The generated document (formatted in Asciidoc in this case) contains a formatted contract. The location of this file would be index/dsl-contract.adoc.spring-doc.cadn.net.cn

Specifying the priority attribute

The method SpringCloudContractRestDocs.dslContract() takes an optional Map parameter that allows you to specify additional attributes in the template.spring-doc.cadn.net.cn

One of these attributes is the priority field that you may specify as follows:spring-doc.cadn.net.cn

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

Overriding the DSL contract template

By default, the output of the contract is based on a file named default-dsl-contract-only.snippet.spring-doc.cadn.net.cn

You may provide a custom template file instead by overriding the getTemplate() method as follows:spring-doc.cadn.net.cn

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

so the example above showing this linespring-doc.cadn.net.cn

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

should be changed to:spring-doc.cadn.net.cn

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

Templates are resolved by looking for resources on the classpath. The following locations are checked in order:spring-doc.cadn.net.cn

Therefore in the example above you should place a file named custom-dsl-template.snippet in src/test/resources/org/springframework/restdocs/templates/custom-dsl-template.snippetspring-doc.cadn.net.cn