Add support for WireMock matchers on restdocs stub builder

This commit is contained in:
Dave Syer
2016-07-27 09:16:12 +01:00
parent 4e21cbd884
commit 5609ffe76c
6 changed files with 154 additions and 23 deletions

View File

@@ -157,6 +157,29 @@ 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.
Instead of the `jsonPath` and `contentType` convenience methods, you
can also use WireMock to verify the request matches the created
stub. Example:
[source,java,indent=0]
----
@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"))
.stub("resource"));
}
----
The WireMock API is rich - you can match headers, query parameters,
and request body by regex for instance - so this can useful to create
stubs with a wider range of parameters.
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

View File

@@ -5,9 +5,6 @@ buildscript {
maven { url "http://repo.spring.io/libs-snapshot-local" }
maven { url "http://repo.spring.io/libs-release-local" }
maven { url "http://repo.spring.io/libs-staging-local" }
maven { url 'http://repo.spring.io/plugins-snapshot' }
maven { url "http://repo.spring.io/plugins-release-local" }
maven { url "http://repo.spring.io/plugins-staging-local/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:1.4.0.BUILD-SNAPSHOT"
@@ -41,7 +38,8 @@ dependencies {
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile 'org.springframework.cloud:spring-cloud-contract-wiremock'
testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "com.example:http-server-restdocs:0.0.1-SNAPSHOT:stubs"
}
test {

View File

@@ -141,22 +141,6 @@
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-plugin-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/plugins-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-plugin-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/plugins-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

View File

@@ -25,6 +25,14 @@ echo -e "\n\nBuilding client (uses Spring Cloud Contract Stub Runner)"
cd dsl/http-client
./gradlew clean build -PverifierVersion=${VERIFIER_VERSION} --stacktrace
cd $ROOT
echo -e "Building server (uses Spring Cloud Contract Verifier Gradle Plugin)"
cd restdocs/http-server
./gradlew clean build publishToMavenLocal -PverifierVersion=${VERIFIER_VERSION} --stacktrace
cd $ROOT
echo -e "\n\nBuilding client (uses Spring Cloud Contract Stub Runner)"
cd restdocs/http-client
./gradlew clean build -PverifierVersion=${VERIFIER_VERSION} --stacktrace
cd $ROOT
echo -e "\n\nClearing saved stubs"
rm -rf $LOCAL_MAVEN_REPO/repository/org/springframework/cloud/contract/testprojects/

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.contract.wiremock.restdocs;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -25,6 +26,7 @@ import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
@@ -32,6 +34,11 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import com.github.tomakehurst.wiremock.client.RemoteMappingBuilder;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.matching.MatchResult;
import com.github.tomakehurst.wiremock.servlet.WireMockHttpServletRequestAdapter;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import com.jayway.jsonpath.JsonPath;
public class ContractRequestHandler implements ResultHandler {
@@ -42,6 +49,8 @@ public class ContractRequestHandler implements ResultHandler {
private MediaType contentType;
private String name;
private RemoteMappingBuilder<?, ?> builder;
public ContractRequestHandler() {
}
@@ -55,7 +64,7 @@ public class ContractRequestHandler implements ResultHandler {
@Override
public void handle(MvcResult result) throws Exception {
MockHttpServletRequest request = result.getRequest();
Map<String,Object> configuration = getConfiguration(result);
Map<String, Object> configuration = getConfiguration(result);
String actual = StreamUtils.copyToString(request.getInputStream(),
Charset.forName("UTF-8"));
for (JsonPath jsonPath : jsonPaths.values()) {
@@ -69,19 +78,50 @@ public class ContractRequestHandler implements ResultHandler {
assertThat(contentType.includes(MediaType.valueOf(resultType))).isTrue()
.as("content type did not match");
}
if (builder != null) {
builder.willReturn(getResponseDefinition(result));
StubMapping stubMapping = builder.build();
MatchResult match = stubMapping.getRequest()
.match(new WireMockHttpServletRequestAdapter(request));
assertThat(match.isExactMatch()).as("wiremock did not match request").isTrue();
configuration.put("contract.stubMapping", stubMapping);
}
MockMvcRestDocumentation.document(this.name).handle(result);
}
private ResponseDefinitionBuilder getResponseDefinition(MvcResult result)
throws UnsupportedEncodingException {
MockHttpServletResponse response = result.getResponse();
ResponseDefinitionBuilder definition = ResponseDefinitionBuilder
.responseDefinition().withBody(response.getContentAsString())
.withStatus(response.getStatus());
addResponseHeaders(definition, response);
return definition;
}
private void addResponseHeaders(ResponseDefinitionBuilder definition,
MockHttpServletResponse input) {
for (String name : input.getHeaderNames()) {
definition.withHeader(name, input.getHeader(name));
}
}
private Map<String, Object> getConfiguration(MvcResult result) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) result.getRequest().getAttribute(ATTRIBUTE_NAME_CONFIGURATION);
if (map==null) {
Map<String, Object> map = (Map<String, Object>) result.getRequest()
.getAttribute(ATTRIBUTE_NAME_CONFIGURATION);
if (map == null) {
map = new HashMap<>();
result.getRequest().setAttribute(ATTRIBUTE_NAME_CONFIGURATION, map);
}
return map;
}
public ContractRequestHandler wiremock(RemoteMappingBuilder<?, ?> builder) {
this.builder = builder;
return this;
}
public ContractRequestHandler jsonPath(String expression, Object... args) {
compile(expression, args);
return this;

View File

@@ -0,0 +1,78 @@
package org.springframework.cloud.contract.wiremock;
import org.junit.ComparisonFailure;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.WiremockServerRestDocsMatcherApplicationTests.TestConfiguration;
import org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs;
import org.springframework.context.annotation.Configuration;
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 org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.github.tomakehurst.wiremock.client.WireMock;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
@AutoConfigureMockMvc
@DirtiesContext
public class WiremockServerRestDocsMatcherApplicationTests {
@Autowired
private MockMvc mockMvc;
@Rule
public ExpectedException expected = ExpectedException.none();
@Test
public void matchesRequest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/resource").content("greeting")
.contentType(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
.andDo(WireMockRestDocs.verify()
.wiremock(WireMock.post(WireMock.urlPathEqualTo("/resource"))
.withRequestBody(WireMock.matching("greeting.*")))
.stub("posted"));
}
@Test
public void doesNotMatch() throws Exception {
expected.expect(ComparisonFailure.class);
expected.expectMessage("wiremock did not match");
mockMvc.perform(MockMvcRequestBuilders.post("/resource").content("greeting")
.contentType(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
.andDo(WireMockRestDocs.verify()
.wiremock(WireMock.post(WireMock.urlPathEqualTo("/resource"))
.withRequestBody(WireMock.matching("garbage.*")))
.stub("posted"));
}
@Configuration
@RestController
protected static class TestConfiguration {
@ResponseBody
@RequestMapping(value = "/resource", method = RequestMethod.POST)
public String resource(@RequestBody String body) {
return "Hello World";
}
}
}