From 4305f40fd9874828a21af044d36353a8a3bb72fc Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 26 Jul 2016 10:44:20 +0100 Subject: [PATCH] Expose WireMockServer for @Autowired in test class --- .../standalone/restdocs/http-client/pom.xml | 4 ++ .../loan/LoanApplicationServiceTests.java | 42 ++++++++++--------- .../http-server/src/assembly/stub.xml | 2 +- .../com/example/fraud/StubGeneratorTests.java | 27 +++++------- .../wiremock/ContractRequestHandler.java | 22 +++++++--- .../contract/wiremock/RestDocsContracts.java | 4 +- .../wiremock/WireMockConfiguration.java | 9 +++- .../WireMockRestDocsConfiguration.java | 8 +--- .../contract/wiremock/WireMockSnippet.java | 19 ++++----- 9 files changed, 75 insertions(+), 62 deletions(-) diff --git a/samples/standalone/restdocs/http-client/pom.xml b/samples/standalone/restdocs/http-client/pom.xml index 87df44efca..d238266617 100644 --- a/samples/standalone/restdocs/http-client/pom.xml +++ b/samples/standalone/restdocs/http-client/pom.xml @@ -53,6 +53,10 @@ + + org.springframework.boot + spring-boot-starter-activemq + diff --git a/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java b/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java index f7f02a2ab6..92dda26732 100644 --- a/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java +++ b/samples/standalone/restdocs/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java @@ -2,34 +2,46 @@ package com.example.loan; import static org.assertj.core.api.Assertions.assertThat; +import java.nio.charset.Charset; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; -import org.springframework.cloud.contract.wiremock.WireMockSpring; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; +import org.springframework.core.io.Resource; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.StreamUtils; import com.example.loan.model.Client; import com.example.loan.model.LoanApplication; import com.example.loan.model.LoanApplicationResult; import com.example.loan.model.LoanApplicationStatus; -import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; @RunWith(SpringRunner.class) -@SpringBootTest(classes=TestConfiguration.class) +@SpringBootTest @AutoConfigureWireMock public class LoanApplicationServiceTests { @Autowired private LoanApplicationService sut; + @Value("classpath:META-INF/com.example/http-server-restdocs/0.0.1-SNAPSHOT/mappings/markClientAsFraud.json") + private Resource markClientAsFraud; + + @Value("classpath:META-INF/com.example/http-server-restdocs/0.0.1-SNAPSHOT/mappings/markClientAsNotFraud.json") + private Resource markClientAsNotFraud; + + @Autowired + private WireMockServer server; + @Test - public void shouldSuccessfullyApplyForLoan() { + public void shouldSuccessfullyApplyForLoan() throws Exception { + server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString( + markClientAsNotFraud.getInputStream(), Charset.forName("UTF-8")))); // given: LoanApplication application = new LoanApplication(new Client("1234567890"), 123.123); @@ -42,7 +54,9 @@ public class LoanApplicationServiceTests { } @Test - public void shouldBeRejectedDueToAbnormalLoanAmount() { + public void shouldBeRejectedDueToAbnormalLoanAmount() throws Exception { + server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString( + markClientAsFraud.getInputStream(), Charset.forName("UTF-8")))); // given: LoanApplication application = new LoanApplication(new Client("1234567890"), 99999); @@ -54,14 +68,4 @@ public class LoanApplicationServiceTests { assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high"); } - @Configuration - @Import(Application.class) - protected static class TestConfiguration { - - @Bean - public Options wiremockOptions() { - return WireMockSpring.options().usingFilesUnderClasspath("META-INF/wiremock"); - } - - } } diff --git a/samples/standalone/restdocs/http-server/src/assembly/stub.xml b/samples/standalone/restdocs/http-server/src/assembly/stub.xml index 235d026263..914f265c70 100644 --- a/samples/standalone/restdocs/http-server/src/assembly/stub.xml +++ b/samples/standalone/restdocs/http-server/src/assembly/stub.xml @@ -24,7 +24,7 @@ ${project.build.directory}/snippets/stubs - META-INF/wiremock/mappings/${project.artifactId} + META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings **/* 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 4e11fe4440..e4b92cca5a 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,5 +1,8 @@ package com.example.fraud; +import static org.springframework.cloud.contract.wiremock.RestDocsContracts.verify; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + import java.math.BigDecimal; import org.junit.Test; @@ -10,14 +13,11 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDoc import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.json.JacksonTester; -import org.springframework.cloud.contract.wiremock.RestDocsContracts; -import org.springframework.cloud.contract.wiremock.WireMockSnippet; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import com.example.fraud.model.FraudCheck; @@ -32,9 +32,6 @@ public class StubGeneratorTests { @Autowired private MockMvc mockMvc; - @Autowired - private WireMockSnippet snippet; - private JacksonTester json; @Test @@ -45,11 +42,9 @@ public class StubGeneratorTests { mockMvc.perform(MockMvcRequestBuilders.put("/fraudcheck") .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) .content(json.write(fraudCheck).getJson())) - .andExpect(MockMvcResultMatchers.jsonPath("$.fraudCheckStatus") - .value("FRAUD")) - .andExpect(MockMvcResultMatchers.jsonPath("$.rejectionReason") - .value("Amount too high")) - .andDo(RestDocsContracts.content(snippet).jsonPath("$.clientId") + .andExpect(jsonPath("$.fraudCheckStatus").value("FRAUD")) + .andExpect(jsonPath("$.rejectionReason").value("Amount too high")) + .andDo(verify().jsonPath("$.clientId") .jsonPath("$[?(@.loanAmount > 1000)]") .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) .contract("markClientAsFraud")); @@ -59,15 +54,13 @@ public class StubGeneratorTests { public void shouldMarkClientAsNotFraud() throws Exception { FraudCheck fraudCheck = new FraudCheck(); fraudCheck.setClientId("1234567890"); - fraudCheck.setLoanAmount(BigDecimal.valueOf(1)); + fraudCheck.setLoanAmount(BigDecimal.valueOf(123.123)); mockMvc.perform(MockMvcRequestBuilders.put("/fraudcheck") .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) .content(json.write(fraudCheck).getJson())) - .andExpect( - MockMvcResultMatchers.jsonPath("$.fraudCheckStatus").value("OK")) - .andExpect(MockMvcResultMatchers.jsonPath("$.rejectionReason") - .doesNotExist()) - .andDo(RestDocsContracts.content(snippet).jsonPath("$.clientId") + .andExpect(jsonPath("$.fraudCheckStatus").value("OK")) + .andExpect(jsonPath("$.rejectionReason").doesNotExist()) + .andDo(verify().jsonPath("$.clientId") .jsonPath("$[?(@.loanAmount <= 1000)]") .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) .contract("markClientAsNotFraud")); diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/ContractRequestHandler.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/ContractRequestHandler.java index 804414f8c2..0b3cdc221d 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/ContractRequestHandler.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/ContractRequestHandler.java @@ -19,6 +19,7 @@ package org.springframework.cloud.contract.wiremock; import static org.assertj.core.api.Assertions.assertThat; import java.nio.charset.Charset; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -35,13 +36,13 @@ import com.jayway.jsonpath.JsonPath; public class ContractRequestHandler implements ResultHandler { + static final String ATTRIBUTE_NAME_CONFIGURATION = "org.springframework.restdocs.configuration"; + private Map jsonPaths = new LinkedHashMap<>(); private MediaType contentType; - private WireMockSnippet snippet; private String name; - public ContractRequestHandler(WireMockSnippet snippet) { - this.snippet = snippet; + public ContractRequestHandler() { } public ResultHandler contract(String name) { @@ -54,14 +55,15 @@ public class ContractRequestHandler implements ResultHandler { @Override public void handle(MvcResult result) throws Exception { MockHttpServletRequest request = result.getRequest(); + Map configuration = getConfiguration(result); String actual = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8")); for (JsonPath jsonPath : jsonPaths.values()) { new JsonPathValue(jsonPath, actual).assertHasValue(Object.class, "an object"); } - snippet.setJsonPaths(jsonPaths.keySet()); + configuration.put("contract.jsonPaths", jsonPaths.keySet()); if (contentType != null) { - snippet.setContentType(contentType); + configuration.put("contract.contentType", contentType); String resultType = request.getContentType(); assertThat(resultType).isNotNull().as("no content type"); assertThat(contentType.includes(MediaType.valueOf(resultType))).isTrue() @@ -70,6 +72,16 @@ public class ContractRequestHandler implements ResultHandler { MockMvcRestDocumentation.document(this.name).handle(result); } + private Map getConfiguration(MvcResult result) { + @SuppressWarnings("unchecked") + Map map = (Map) result.getRequest().getAttribute(ATTRIBUTE_NAME_CONFIGURATION); + if (map==null) { + map = new HashMap<>(); + result.getRequest().setAttribute(ATTRIBUTE_NAME_CONFIGURATION, map); + } + return map; + } + public ContractRequestHandler jsonPath(String expression, Object... args) { compile(expression, args); return this; diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/RestDocsContracts.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/RestDocsContracts.java index 48b9f10ef4..017d71a8ab 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/RestDocsContracts.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/RestDocsContracts.java @@ -22,8 +22,8 @@ package org.springframework.cloud.contract.wiremock; */ public class RestDocsContracts { - public static ContractRequestHandler content(WireMockSnippet snippet) { - return new ContractRequestHandler(snippet); + public static ContractRequestHandler verify() { + return new ContractRequestHandler(); } } 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 34e7e61a56..ca6dab5bc1 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 @@ -20,6 +20,7 @@ import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; @@ -49,7 +50,10 @@ public class WireMockConfiguration implements SmartLifecycle, ImportAware { @Value("${wiremock.server.https-port:-1}") private int httpsPort = -1; - + + @Autowired + private DefaultListableBeanFactory beanFactory; + @Override public void setImportMetadata(AnnotationMetadata metadata) { AnnotationAttributes map = AnnotationAttributes.fromMap( @@ -77,6 +81,9 @@ public class WireMockConfiguration implements SmartLifecycle, ImportAware { this.options = factory; } server = new WireMockServer(options); + if (!beanFactory.containsBean("wireMockServer")) { + beanFactory.registerSingleton("wireMockServer", server); + } } @Override diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestDocsConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestDocsConfiguration.java index 6fe2838c30..b21bbcca83 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestDocsConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestDocsConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.cloud.contract.wiremock; import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; @@ -31,12 +30,7 @@ public class WireMockRestDocsConfiguration @Override public void customize(MockMvcRestDocumentationConfigurer configurer) { - configurer.snippets().withAdditionalDefaults(wireMockSnippet()); - } - - @Bean - public WireMockSnippet wireMockSnippet() { - return new WireMockSnippet(); + configurer.snippets().withAdditionalDefaults(new WireMockSnippet()); } } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSnippet.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSnippet.java index b936169997..eede0fbd91 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSnippet.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSnippet.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Arrays; -import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -63,6 +62,7 @@ public class WireMockSnippet implements Snippet { @Override public void document(Operation operation) throws IOException { + extractMatchers(operation); String json = Json .write(request(operation).willReturn(response(operation)).build()); RestDocumentationContext context = (RestDocumentationContext) operation @@ -75,6 +75,14 @@ public class WireMockSnippet implements Snippet { } } + private void extractMatchers(Operation operation) { + @SuppressWarnings("unchecked") + Set jsonPaths = (Set) operation.getAttributes() + .get("contract.jsonPaths"); + this.jsonPaths = jsonPaths; + contentType = (MediaType) operation.getAttributes().get("contract.contentType"); + } + private ResponseDefinitionBuilder response(Operation operation) { return aResponse().withHeaders(responseHeaders(operation)) .withBody(operation.getResponse().getContentAsString()); @@ -141,7 +149,6 @@ public class WireMockSnippet implements Snippet { .getHeaders(); HttpHeaders result = new HttpHeaders(); for (String name : headers.keySet()) { - // TODO: whitelist the headers if (!headerBlackList.contains(name.toLowerCase())) { result = result.plus(new HttpHeader(name, headers.get(name))); } @@ -149,12 +156,4 @@ public class WireMockSnippet implements Snippet { return result; } - public void setJsonPaths(Collection jsonPaths) { - this.jsonPaths = new LinkedHashSet<>(jsonPaths); - } - - public void setContentType(MediaType contentType) { - this.contentType = contentType; - } - } \ No newline at end of file