Add support for WireMock matchers on restdocs stub builder
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user