Reduce duplication by introducing RestDocumentationHandler

Previously, logic for creating an Operation, determining the snippets
to call, and calling them was duplicated in both the MockMvc and
REST Assured modules. This commit introduces a new core class,
RestDocumentationHandler, that now does the bulk of the work in a
reusable manner. The MockMvc and REST Assured modules have been
updated to delegate to RestDocumentationHandler.

Closes gh-194
This commit is contained in:
Andy Wilkinson
2016-02-04 11:33:40 +00:00
parent 27f919748b
commit a9c9bad15e
17 changed files with 662 additions and 280 deletions

View File

@@ -30,20 +30,23 @@ import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationRequestPartFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.RequestConverter;
import com.jayway.restassured.response.Header;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.MultiPartSpecification;
/**
* A factory for creating an {@link OperationRequest} derived from a REST Assured
* A converter for creating an {@link OperationRequest} from a REST Assured
* {@link FilterableRequestSpecification}.
*
* @author Andy Wilkinson
*/
class RestAssuredOperationRequestFactory {
class RestAssuredRequestConverter implements
RequestConverter<FilterableRequestSpecification> {
OperationRequest createOperationRequest(FilterableRequestSpecification requestSpec) {
@Override
public OperationRequest convert(FilterableRequestSpecification requestSpec) {
return new OperationRequestFactory().create(URI.create(requestSpec.getURI()),
HttpMethod.valueOf(requestSpec.getMethod().name()),
extractContent(requestSpec), extractHeaders(requestSpec),

View File

@@ -20,19 +20,21 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.ResponseConverter;
import com.jayway.restassured.response.Header;
import com.jayway.restassured.response.Response;
/**
* A factory for creating an {@link OperationResponse} derived from a REST Assured
* A converter for creating an {@link OperationResponse} from a REST Assured
* {@link Response}.
*
* @author Andy Wilkinson
*/
class RestAssuredOperationResponseFactory {
class RestAssuredResponseConverter implements ResponseConverter<Response> {
OperationResponse createOperationResponse(Response response) {
@Override
public OperationResponse convert(Response response) {
return new OperationResponseFactory().create(
HttpStatus.valueOf(response.getStatusCode()), extractHeaders(response),
extractContent(response));

View File

@@ -17,6 +17,7 @@
package org.springframework.restdocs.restassured;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.RestDocumentationHandler;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.snippet.Snippet;
@@ -28,6 +29,10 @@ import org.springframework.restdocs.snippet.Snippet;
*/
public abstract class RestAssuredRestDocumentation {
private static final RestAssuredRequestConverter REQUEST_CONVERTER = new RestAssuredRequestConverter();
private static final RestAssuredResponseConverter RESPONSE_CONVERTER = new RestAssuredResponseConverter();
private RestAssuredRestDocumentation() {
}
@@ -41,7 +46,8 @@ public abstract class RestAssuredRestDocumentation {
* @return a {@link RestDocumentationFilter} that will produce the documentation
*/
public static RestDocumentationFilter document(String identifier, Snippet... snippets) {
return new RestDocumentationFilter(identifier, snippets);
return new RestDocumentationFilter(new RestDocumentationHandler<>(identifier,
REQUEST_CONVERTER, RESPONSE_CONVERTER, snippets));
}
/**
@@ -56,7 +62,8 @@ public abstract class RestAssuredRestDocumentation {
*/
public static RestDocumentationFilter document(String identifier,
OperationRequestPreprocessor requestPreprocessor, Snippet... snippets) {
return new RestDocumentationFilter(identifier, requestPreprocessor, snippets);
return new RestDocumentationFilter(new RestDocumentationHandler<>(identifier,
REQUEST_CONVERTER, RESPONSE_CONVERTER, requestPreprocessor, snippets));
}
/**
@@ -71,7 +78,8 @@ public abstract class RestAssuredRestDocumentation {
*/
public static RestDocumentationFilter document(String identifier,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
return new RestDocumentationFilter(identifier, responsePreprocessor, snippets);
return new RestDocumentationFilter(new RestDocumentationHandler<>(identifier,
REQUEST_CONVERTER, RESPONSE_CONVERTER, responsePreprocessor, snippets));
}
/**
@@ -89,8 +97,9 @@ public abstract class RestAssuredRestDocumentation {
public static RestDocumentationFilter document(String identifier,
OperationRequestPreprocessor requestPreprocessor,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
return new RestDocumentationFilter(identifier, requestPreprocessor,
responsePreprocessor, snippets);
return new RestDocumentationFilter(new RestDocumentationHandler<>(identifier,
REQUEST_CONVERTER, RESPONSE_CONVERTER, requestPreprocessor,
responsePreprocessor, snippets));
}
/**

View File

@@ -16,22 +16,13 @@
package org.springframework.restdocs.restassured;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.SnippetConfigurer;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.StandardOperation;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.RestDocumentationHandler;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.util.Assert;
import com.jayway.restassured.filter.Filter;
import com.jayway.restassured.filter.FilterContext;
@@ -46,40 +37,12 @@ import com.jayway.restassured.specification.FilterableResponseSpecification;
*/
public final class RestDocumentationFilter implements Filter {
private final String identifier;
private final RestDocumentationHandler<FilterableRequestSpecification, Response> delegate;
private final OperationRequestPreprocessor requestPreprocessor;
private final OperationResponsePreprocessor responsePreprocessor;
private final List<Snippet> snippets;
RestDocumentationFilter(String identifier, Snippet... snippets) {
this(identifier, new IdentityOperationRequestPreprocessor(),
new IdentityOperationResponsePreprocessor(), snippets);
}
RestDocumentationFilter(String identifier,
OperationRequestPreprocessor operationRequestPreprocessor,
Snippet... snippets) {
this(identifier, operationRequestPreprocessor,
new IdentityOperationResponsePreprocessor(), snippets);
}
RestDocumentationFilter(String identifier,
OperationResponsePreprocessor operationResponsePreprocessor,
Snippet... snippets) {
this(identifier, new IdentityOperationRequestPreprocessor(),
operationResponsePreprocessor, snippets);
}
RestDocumentationFilter(String identifier,
OperationRequestPreprocessor requestPreprocessor,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
this.identifier = identifier;
this.requestPreprocessor = requestPreprocessor;
this.responsePreprocessor = responsePreprocessor;
this.snippets = new ArrayList<>(Arrays.asList(snippets));
RestDocumentationFilter(
RestDocumentationHandler<FilterableRequestSpecification, Response> delegate) {
Assert.notNull(delegate, "delegate must be non-null");
this.delegate = delegate;
}
@Override
@@ -87,35 +50,15 @@ public final class RestDocumentationFilter implements Filter {
FilterableResponseSpecification responseSpec, FilterContext context) {
Response response = context.next(requestSpec, responseSpec);
OperationRequest operationRequest = this.requestPreprocessor
.preprocess(new RestAssuredOperationRequestFactory()
.createOperationRequest(requestSpec));
OperationResponse operationResponse = this.responsePreprocessor
.preprocess(new RestAssuredOperationResponseFactory()
.createOperationResponse(response));
RestDocumentationContext documentationContext = context
.getValue(RestDocumentationContext.class.getName());
Map<String, Object> attributes = new HashMap<>();
attributes.put(RestDocumentationContext.class.getName(), documentationContext);
attributes.put("org.springframework.restdocs.urlTemplate",
Map<String, Object> configuration = new HashMap<>(
context.<Map<String, Object>>getValue("org.springframework.restdocs.configuration"));
configuration.put(RestDocumentationContext.class.getName(), context
.<RestDocumentationContext>getValue(RestDocumentationContext.class
.getName()));
configuration.put("org.springframework.restdocs.urlTemplate",
requestSpec.getUserDefinedPath());
Map<String, Object> configuration = context
.getValue("org.springframework.restdocs.configuration");
attributes.putAll(configuration);
Operation operation = new StandardOperation(this.identifier, operationRequest,
operationResponse, attributes);
try {
for (Snippet snippet : getSnippets(configuration)) {
snippet.document(operation);
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
this.delegate.handle(requestSpec, response, configuration);
return response;
}
@@ -128,37 +71,8 @@ public final class RestDocumentationFilter implements Filter {
* @return this {@code RestDocumentationFilter}
*/
public RestDocumentationFilter snippets(Snippet... snippets) {
this.snippets.addAll(Arrays.asList(snippets));
this.delegate.addSnippets(snippets);
return this;
}
@SuppressWarnings("unchecked")
private List<Snippet> getSnippets(Map<String, Object> configuration) {
List<Snippet> combinedSnippets = new ArrayList<>(
(List<Snippet>) configuration
.get(SnippetConfigurer.ATTRIBUTE_DEFAULT_SNIPPETS));
combinedSnippets.addAll(this.snippets);
return combinedSnippets;
}
private static final class IdentityOperationRequestPreprocessor implements
OperationRequestPreprocessor {
@Override
public OperationRequest preprocess(OperationRequest request) {
return request;
}
}
private static final class IdentityOperationResponsePreprocessor implements
OperationResponsePreprocessor {
@Override
public OperationResponse preprocess(OperationResponse response) {
return response;
}
}
}

View File

@@ -36,7 +36,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.restassured.RestAssuredOperationRequestFactoryTests.TestApplication;
import org.springframework.restdocs.restassured.RestAssuredRequestConverterTests.TestApplication;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -51,7 +51,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RestAssuredOperationRequestFactory}.
* Tests for {@link RestAssuredRequestConverter}.
*
* @author Andy Wilkinson
*/
@@ -59,12 +59,12 @@ import static org.junit.Assert.assertThat;
@SpringApplicationConfiguration(classes = TestApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
public class RestAssuredOperationRequestFactoryTests {
public class RestAssuredRequestConverterTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final RestAssuredOperationRequestFactory factory = new RestAssuredOperationRequestFactory();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@Value("${local.server.port}")
private int port;
@@ -74,7 +74,7 @@ public class RestAssuredOperationRequestFactoryTests {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.get("/foo/bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getUri(),
is(equalTo(URI.create("http://localhost:" + this.port + "/foo/bar"))));
}
@@ -84,7 +84,7 @@ public class RestAssuredOperationRequestFactoryTests {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.head("/foo/bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getMethod(), is(equalTo(HttpMethod.HEAD)));
}
@@ -94,7 +94,7 @@ public class RestAssuredOperationRequestFactoryTests {
.queryParam("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@@ -104,7 +104,7 @@ public class RestAssuredOperationRequestFactoryTests {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.get("/?foo=bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@@ -115,7 +115,7 @@ public class RestAssuredOperationRequestFactoryTests {
.formParameter("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@@ -126,7 +126,7 @@ public class RestAssuredOperationRequestFactoryTests {
.parameter("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@@ -137,7 +137,7 @@ public class RestAssuredOperationRequestFactoryTests {
.header("Foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getHeaders().toString(), request.getHeaders().size(), is(3));
assertThat(request.getHeaders().get("Foo"), is(equalTo(Arrays.asList("bar"))));
assertThat(request.getHeaders().get("Accept"), is(equalTo(Arrays.asList("*/*"))));
@@ -152,7 +152,7 @@ public class RestAssuredOperationRequestFactoryTests {
.multiPart("b", new ObjectBody("bar"), "application/json");
requestSpec.post().then().statusCode(200);
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
Collection<OperationRequestPart> parts = request.getParts();
assertThat(parts.size(), is(2));
Iterator<OperationRequestPart> iterator = parts.iterator();
@@ -174,7 +174,7 @@ public class RestAssuredOperationRequestFactoryTests {
RequestSpecification requestSpec = RestAssured.given().body("body".getBytes())
.port(this.port);
requestSpec.post();
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
@@ -183,7 +183,7 @@ public class RestAssuredOperationRequestFactoryTests {
.port(this.port);
requestSpec.post();
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString(), is(equalTo("body")));
}
@@ -193,7 +193,7 @@ public class RestAssuredOperationRequestFactoryTests {
.body(new ObjectBody("bar")).port(this.port);
requestSpec.post();
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString(), is(equalTo("{\"foo\":\"bar\"}")));
}
@@ -205,7 +205,7 @@ public class RestAssuredOperationRequestFactoryTests {
requestSpec.post();
this.thrown
.expectMessage(equalTo("Unsupported request content: java.io.ByteArrayInputStream"));
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
@@ -214,7 +214,7 @@ public class RestAssuredOperationRequestFactoryTests {
.body(new File("src/test/resources/body.txt")).port(this.port);
requestSpec.post();
this.thrown.expectMessage(equalTo("Unsupported request content: java.io.File"));
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
/**