Add some docs for restdocs usage and rename some things

This commit is contained in:
Dave Syer
2016-07-26 15:05:12 +01:00
parent aa1d7dc074
commit f46666324b
9 changed files with 175 additions and 43 deletions

View File

@@ -86,4 +86,78 @@ Currently we support Tomcat, Jetty and Undertow as Spring Boot
embedded servers, and Wiremock itself has "native" support for a
particular version of Jetty (currently 9.2). To use the native Jetty
you need to add the native wiremock dependencies and exclude the
Spring Boot container if there is one.
Spring Boot container if there is one.
== Generating Stubs using RestDocs
https://projects.spring.io/spring-restdocs[Spring RestDocs] can be
used to generate documentation (e.g. in asciidoctor format) for an
HTTP API with Spring MockMvc or RestEasy. At the same time as you
generate documentation for your API, you can also generate WireMock
stubs, by using Spring Cloud Contract WireMock. Just write your normal
RestDocs test cases and use `@AutoConfigureRestDocs` to have stubs
automatically in the restdocs output directory. For example:
[source,java,indent=0]
----
@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"));
}
}
----
From this test will be generated a WireMock stub at
"target/snippets/stubs/resource.json". It matches all GET requests to
the "/resource" path.
To create stubs for PUT and POST it is useful to be able to match the
body of the request as well. The main entry point for this is
`WireMockRestDocs.verify()` which can be used as a substitute for the
`document()` convenience method. For example:
[source,java,indent=0]
----
@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"));
}
}
----
The `jsonPath()` method does 2 things: 1) asserts that the request
body in the test itself actually contains the JSON it matches, and 2)
makes a WireMock request matcher using the same JSON. So this contract
is saying: any valid POST with an "id" field will get back an the same
response as in this test. You can chain together calls to
`.jsonPath()` to add additional matchers.
On the consumer side, assuming the `resource.json` generated above is
available on the classpath, you can create a stub using WireMock in a
number of different ways, including as described above using
`@AutoConfigureWireMock(stubs="classpath:resource.json")`.

View File

@@ -1,6 +1,6 @@
package com.example.fraud;
import static org.springframework.cloud.contract.wiremock.restdocs.RestDocsContracts.verify;
import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import java.math.BigDecimal;
@@ -47,7 +47,7 @@ public class StubGeneratorTests {
.andDo(verify().jsonPath("$.clientId")
.jsonPath("$[?(@.loanAmount > 1000)]")
.contentType(MediaType.valueOf("application/vnd.fraud.v1+json"))
.contract("markClientAsFraud"));
.stub("markClientAsFraud"));
}
@Test
@@ -63,7 +63,7 @@ public class StubGeneratorTests {
.andDo(verify().jsonPath("$.clientId")
.jsonPath("$[?(@.loanAmount <= 1000)]")
.contentType(MediaType.valueOf("application/vnd.fraud.v1+json"))
.contract("markClientAsNotFraud"));
.stub("markClientAsNotFraud"));
}
}

View File

@@ -25,7 +25,14 @@ import java.lang.annotation.Target;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.context.annotation.Import;
import com.github.tomakehurst.wiremock.core.Options;
/**
* Annotation for test classes that want to start a WireMock server as part of the Spring
* Application Context. The port, https port and stub locations (if any) can all be
* controlled directly here. For more fine-grained control of the server instance add a
* bean of type {@link Options} to the application context.
*
* @author Dave Syer
*
*/
@@ -35,11 +42,11 @@ import org.springframework.context.annotation.Import;
@Import(WireMockConfiguration.class)
@PropertyMapping("wiremock.server")
public @interface AutoConfigureWireMock {
int port() default 8080;
int httpsPort() default -1;
String stubs() default "";
}

View File

@@ -39,6 +39,13 @@ import com.github.tomakehurst.wiremock.core.Options;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
/**
* Configuration and lifecycle for a Spring Application context that wants to run a
* WireMock server. Can be used by adding
* {@link AutoConfigureWireMock @AutoConfigureWireMock} to a Spring Boot JUnit test. To
* configure the properties of the wiremock server you can use the AutoConfigureWireMock
* annotation, or add a bean of type {@link Options} (via
* {@link WireMockSpring#options()}) to your test context.
*
* @author Dave Syer
*
*/

View File

@@ -34,6 +34,10 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Convenience class for loading WireMock stubs into a {@link MockRestServiceServer}. In
* this way using a {@link RestTemplate} can mock the responses from a server using
* WireMock JSON DSL instead of the native Java DSL.
*
* @author Dave Syer
*
*/
@@ -42,7 +46,7 @@ public class WireMockRestServiceServer {
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
private String suffix = ".json";
private String baseUrl = "";
private MockRestServiceServer server;
@@ -64,7 +68,8 @@ public class WireMockRestServiceServer {
public MockRestServiceServer stubs(String... locations) {
for (String location : locations) {
try {
if (!StringUtils.getFilename(location).contains(".") && !location.contains("*")) {
if (!StringUtils.getFilename(location).contains(".")
&& !location.contains("*")) {
if (!location.endsWith("/")) {
location = location + "/";
}
@@ -72,14 +77,19 @@ public class WireMockRestServiceServer {
}
for (Resource resource : this.resolver.getResources(location)) {
StubMapping mapping;
mapping = Json.read(StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset()),
StubMapping.class);
this.server.expect(requestTo(this.baseUrl + mapping.getRequest().getUrlPath()))
.andRespond(withSuccess(mapping.getResponse().getBody(), MediaType.TEXT_PLAIN));
mapping = Json
.read(StreamUtils.copyToString(resource.getInputStream(),
Charset.defaultCharset()), StubMapping.class);
this.server
.expect(requestTo(
this.baseUrl + mapping.getRequest().getUrlPath()))
.andRespond(withSuccess(mapping.getResponse().getBody(),
MediaType.TEXT_PLAIN));
}
}
catch (IOException e) {
throw new IllegalStateException("Cannot load resources for: " + location, e);
throw new IllegalStateException("Cannot load resources for: " + location,
e);
}
}
return this.server;

View File

@@ -45,7 +45,7 @@ public class ContractRequestHandler implements ResultHandler {
public ContractRequestHandler() {
}
public ResultHandler contract(String name) {
public ResultHandler stub(String name) {
this.name = name;
// TODO: try and get access to the internals of this so we don't need to store
// state in the snippet

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.wiremock.restdocs;
/**
* @author Dave Syer
*
*/
public class RestDocsContracts {
public static ContractRequestHandler verify() {
return new ContractRequestHandler();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.wiremock.restdocs;
/**
* Convenience class for setting up RestDocs to record WireMock stubs. Example usage:
*
* <pre>
* &#64;RunWith(SpringRunner.class)
* &#64;SpringBootTest
* &#64;AutoConfigureRestDocs(outputDir = "target/snippets")
* &#64;AutoConfigureMockMvc
* public class WiremockServerRestDocsApplicationTests {
*
* &#64;Autowired
* private MockMvc mockMvc;
*
* &#64;Test
* public void contextLoads() throws Exception {
* mockMvc.perform(get("/resource"))
* .andExpect(content().string("Hello World"))
* .andDo(verify().contract("resource"));
* }
* </pre>
*
* which creates a file "target/snippets/stubs/resource.json" matching any GET request to
* "/resource". To match POST and PUT, you can also specify the content type using
* <code>verify().contentType(...)</code> and JSON content of the body using
* <code>verify().jsonPath(...)</code>.
*
* @author Dave Syer
*
*/
public class WireMockRestDocs {
public static ContractRequestHandler verify() {
return new ContractRequestHandler();
}
}

View File

@@ -16,11 +16,20 @@
package org.springframework.cloud.contract.wiremock.restdocs;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
/**
* Custom configuration for Spring RestDocs that adds a WireMock snippet (for generating
* JSON stubs). Applied automatically if you use
* {@link AutoConfigureRestDocs @AutoConfigureRestDocs} in your test case and this class
* is available. JSON stubs are generated and added to the restdocs path under "stubs".
*
* @see WireMockRestDocs for a convenient entry point for customizing and asserting the
* stub behaviour
*
* @author Dave Syer
*
*/