diff --git a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc index a729de620f..fbdd7f9cab 100644 --- a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc +++ b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc @@ -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. \ No newline at end of file +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")`. \ No newline at end of file diff --git a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java index a7fab3ff87..82be6a0462 100644 --- a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java +++ b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java @@ -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")); } } \ No newline at end of file diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java index ff47a6c607..9b5ee1a566 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java @@ -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 ""; } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java index 0ec8547b58..1e18ce7473 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java @@ -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 * */ diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java index cda673402e..65aa11dc37 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java @@ -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; diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java index fe63572c4c..26dc16763a 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java @@ -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 diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/RestDocsContracts.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/RestDocsContracts.java deleted file mode 100644 index 68d744a58c..0000000000 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/RestDocsContracts.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java new file mode 100644 index 0000000000..4b9c4879f0 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java @@ -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: + * + *
+ * @RunWith(SpringRunner.class)
+ * @SpringBootTest
+ * @AutoConfigureRestDocs(outputDir = "target/snippets")
+ * @AutoConfigureMockMvc
+ * public class WiremockServerRestDocsApplicationTests {
+ * 
+ * 	@Autowired
+ * 	private MockMvc mockMvc;
+ * 
+ * 	@Test
+ * 	public void contextLoads() throws Exception {
+ * 		mockMvc.perform(get("/resource"))
+ * 				.andExpect(content().string("Hello World"))
+ * 				.andDo(verify().contract("resource"));
+ * 	}
+ * 
+ * + * 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 + * verify().contentType(...) and JSON content of the body using + * verify().jsonPath(...). + * + * @author Dave Syer + * + */ +public class WireMockRestDocs { + + public static ContractRequestHandler verify() { + return new ContractRequestHandler(); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java index 51b419295b..51dc7fc276 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java @@ -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 * */