diff --git a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc index 04a1b48e..8f0ffaf0 100644 --- a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc +++ b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc @@ -58,17 +58,17 @@ Then, in each test, any configuration specific to that test can be performed. Fo ---- include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=use] ---- -<1> Document the links specific to the resource that is being tested -<2> The request and response will be preprocessed due to the use of `alwaysDo` above. +<1> The request and response will be preprocessed due to the use of `alwaysDo` above. +<2> Document the links specific to the resource that is being tested [source,java,indent=0,role="secondary"] .REST Assured ---- include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=use] ---- -<1> Document the links specific to the resource that is being tested -<2> The request and response will be preprocessed due to the configuration of the +<1> The request and response will be preprocessed due to the configuration of the `RequestSpecification` in the `setUp` method. +<2> Document the links specific to the resource that is being tested Various built in preprocessors, including those illustrated above, are available via the static methods on `Preprocessors`. See <> for further details. diff --git a/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java index 18799410..31d88354 100644 --- a/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java @@ -46,16 +46,16 @@ public class EveryTestPreprocessing { // tag::setup[] private MockMvc mockMvc; - private RestDocumentationResultHandler document; + private RestDocumentationResultHandler documentationHandler; @Before public void setup() { - this.document = document("{method-name}", // <1> + this.documentationHandler = document("{method-name}", // <1> preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) - .alwaysDo(this.document) // <2> + .alwaysDo(this.documentationHandler) // <2> .build(); } @@ -63,10 +63,11 @@ public class EveryTestPreprocessing { public void use() throws Exception { // tag::use[] - this.document.snippets( // <1> - links(linkWithRel("self").description("Canonical self link"))); - this.mockMvc.perform(get("/")) // <2> - .andExpect(status().isOk()); + this.mockMvc.perform(get("/")) // <1> + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( // <2> + links(linkWithRel("self").description("Canonical self link")) + )); // end::use[] } diff --git a/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java index e143bcc2..43ef84e7 100644 --- a/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java @@ -44,16 +44,16 @@ public class EveryTestPreprocessing { // tag::setup[] private RequestSpecification spec; - private RestDocumentationFilter document; + private RestDocumentationFilter documentationFilter; @Before public void setup() { - this.document = document("{method-name}", + this.documentationFilter = document("{method-name}", preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())); // <1> this.spec = new RequestSpecBuilder() .addFilter(documentationConfiguration(this.restDocumentation)) - .addFilter(this.document)// <2> + .addFilter(this.documentationFilter)// <2> .build(); } @@ -61,9 +61,9 @@ public class EveryTestPreprocessing { public void use() throws Exception { // tag::use[] - this.document.snippets( // <1> - links(linkWithRel("self").description("Canonical self link"))); - RestAssured.given(this.spec) // <2> + RestAssured.given(this.spec) // <1> + .filter(this.documentationFilter.document( // <2> + links(linkWithRel("self").description("Canonical self link")))) .when().get("/") .then().assertThat().statusCode(is(200)); // end::use[] diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/RestDocumentationGenerator.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/RestDocumentationGenerator.java index 52f9cb09..8a2b0402 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/RestDocumentationGenerator.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/generate/RestDocumentationGenerator.java @@ -41,6 +41,7 @@ import org.springframework.util.Assert; * @param the request type that can be handled * @param the response type that can be handled * @author Andy Wilkinson + * @since 1.1 */ public final class RestDocumentationGenerator { @@ -205,11 +206,27 @@ public final class RestDocumentationGenerator { * called. * * @param snippets the snippets to add + * @deprecated since 1.1 in favor of {@link #withSnippets(Snippet...)} */ + @Deprecated public void addSnippets(Snippet... snippets) { this.additionalSnippets.addAll(Arrays.asList(snippets)); } + /** + * Creates a new {@link RestDocumentationGenerator} with the same configuration as + * this one other than its snippets. The new generator will use the given + * {@code snippets}. + * + * @param snippets the snippets + * @return the new generator + */ + public RestDocumentationGenerator withSnippets(Snippet... snippets) { + return new RestDocumentationGenerator<>(this.identifier, this.requestConverter, + this.responseConverter, this.requestPreprocessor, + this.responsePreprocessor, snippets); + } + @SuppressWarnings("unchecked") private List getSnippets(Map configuration) { List combinedSnippets = new ArrayList<>(this.snippets); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java index fd9b032d..133974a7 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/RestDocumentationGeneratorTests.java @@ -35,6 +35,8 @@ import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; import org.springframework.restdocs.operation.RequestConverter; import org.springframework.restdocs.operation.ResponseConverter; +import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; +import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; import org.springframework.restdocs.snippet.Snippet; import static org.hamcrest.CoreMatchers.equalTo; @@ -43,6 +45,7 @@ import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link RestDocumentationGenerator}. @@ -105,6 +108,7 @@ public class RestDocumentationGeneratorTests { } @Test + @Deprecated public void additionalSnippetsAreCalled() throws IOException { given(this.requestConverter.convert(this.request)) .willReturn(this.operationRequest); @@ -123,6 +127,33 @@ public class RestDocumentationGeneratorTests { verifySnippetInvocation(additionalSnippet2, configuration); } + @Test + public void newGeneratorOnlyCallsItsSnippets() throws IOException { + OperationRequestPreprocessor requestPreprocessor = mock( + OperationRequestPreprocessor.class); + OperationResponsePreprocessor responsePreprocessor = mock( + OperationResponsePreprocessor.class); + given(this.requestConverter.convert(this.request)) + .willReturn(this.operationRequest); + given(this.responseConverter.convert(this.response)) + .willReturn(this.operationResponse); + given(requestPreprocessor.preprocess(this.operationRequest)) + .willReturn(this.operationRequest); + given(responsePreprocessor.preprocess(this.operationResponse)) + .willReturn(this.operationResponse); + Snippet additionalSnippet1 = mock(Snippet.class); + Snippet additionalSnippet2 = mock(Snippet.class); + RestDocumentationGenerator generator = new RestDocumentationGenerator<>( + "id", this.requestConverter, this.responseConverter, requestPreprocessor, + responsePreprocessor, this.snippet); + HashMap configuration = new HashMap<>(); + generator.withSnippets(additionalSnippet1, additionalSnippet2) + .handle(this.request, this.response, configuration); + verifyNoMoreInteractions(this.snippet); + verifySnippetInvocation(additionalSnippet1, configuration); + verifySnippetInvocation(additionalSnippet2, configuration); + } + private void verifySnippetInvocation(Snippet snippet, Map attributes) throws IOException { verifySnippetInvocation(snippet, attributes, 1); diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java index ac43bdd6..5a85fc5c 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java @@ -16,6 +16,7 @@ package org.springframework.restdocs.mockmvc; +import java.util.HashMap; import java.util.Map; import org.springframework.mock.web.MockHttpServletRequest; @@ -59,10 +60,44 @@ public class RestDocumentationResultHandler implements ResultHandler { * * @param snippets the snippets to add * @return this {@code RestDocumentationResultHandler} + * @deprecated since 1.1 in favor of {@link #document(Snippet...)} */ + @Deprecated public RestDocumentationResultHandler snippets(Snippet... snippets) { this.delegate.addSnippets(snippets); return this; } + /** + * Creates a new {@link RestDocumentationResultHandler} that will produce + * documentation using the given {@code snippets}. + * + * @param snippets the snippets + * @return the new result handler + */ + public RestDocumentationResultHandler document(Snippet... snippets) { + return new RestDocumentationResultHandler(this.delegate.withSnippets(snippets)) { + + @Override + public void handle(MvcResult result) throws Exception { + @SuppressWarnings("unchecked") + Map configuration = new HashMap<>( + (Map) result.getRequest() + .getAttribute(ATTRIBUTE_NAME_CONFIGURATION)); + configuration.remove( + RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS); + getDelegate().handle(result.getRequest(), result.getResponse(), + configuration); + } + }; + } + + /** + * Returns the {@link RestDocumentationGenerator} that is used as a delegate. + * + * @return the delegate + */ + protected final RestDocumentationGenerator getDelegate() { + return this.delegate; + } } diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index 5068d34f..5d70cfb5 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -56,6 +56,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -329,6 +331,24 @@ public class MockMvcRestDocumentationIntegrationTests { "http-response.adoc", "curl-request.adoc"); } + @Test + public void alwaysDoWithAdditionalSnippets() throws Exception { + RestDocumentationResultHandler documentation = document("{method-name}-{step}"); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration(this.restDocumentation)) + .alwaysDo(documentation).build(); + + mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(documentation.document( + responseHeaders(headerWithName("a").description("one")))); + + assertExpectedSnippetFilesExist( + new File( + "build/generated-snippets/always-do-with-additional-snippets-1/"), + "http-request.adoc", "http-response.adoc", "curl-request.adoc", + "response-headers.adoc"); + } + @Test public void preprocessedRequest() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java index 3ee03d3f..0d37351e 100644 --- a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java @@ -35,7 +35,7 @@ import org.springframework.util.Assert; * * @author Andy Wilkinson */ -public final class RestDocumentationFilter implements Filter { +public class RestDocumentationFilter implements Filter { static final String CONTEXT_KEY_CONFIGURATION = "org.springframework.restdocs.configuration"; @@ -48,10 +48,27 @@ public final class RestDocumentationFilter implements Filter { } @Override - public Response filter(FilterableRequestSpecification requestSpec, + public final Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext context) { Response response = context.next(requestSpec, responseSpec); + Map configuration = getConfiguration(requestSpec, context); + + this.delegate.handle(requestSpec, response, configuration); + + return response; + } + + /** + * Returns the configuration that should be used when calling the delgate. The + * configuration is derived from the given {@code requestSpec} and {@code context}. + * + * @param requestSpec the request specification + * @param context the filter context + * @return the configuration + */ + protected Map getConfiguration( + FilterableRequestSpecification requestSpec, FilterContext context) { Map configuration = new HashMap<>( context.>getValue(CONTEXT_KEY_CONFIGURATION)); configuration.put(RestDocumentationContext.class.getName(), @@ -59,10 +76,7 @@ public final class RestDocumentationFilter implements Filter { RestDocumentationContext.class.getName())); configuration.put(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, requestSpec.getUserDefinedPath()); - - this.delegate.handle(requestSpec, response, configuration); - - return response; + return configuration; } /** @@ -71,10 +85,35 @@ public final class RestDocumentationFilter implements Filter { * * @param snippets the snippets to add * @return this {@code RestDocumentationFilter} + * @deprecated since 1.1 in favor of {@link #document(Snippet...)} */ - public RestDocumentationFilter snippets(Snippet... snippets) { + @Deprecated + public final RestDocumentationFilter snippets(Snippet... snippets) { this.delegate.addSnippets(snippets); return this; } + /** + * Creates a new {@link RestDocumentationFilter} that will produce documentation using + * the given {@code snippets}. + * + * @param snippets the snippets + * @return the new result handler + */ + public final RestDocumentationFilter document(Snippet... snippets) { + return new RestDocumentationFilter(this.delegate.withSnippets(snippets)) { + + @Override + protected Map getConfiguration( + FilterableRequestSpecification requestSpec, FilterContext context) { + Map configuration = super.getConfiguration(requestSpec, + context); + configuration.remove( + RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS); + return configuration; + } + + }; + } + } diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java index 658793ee..27c055ae 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java @@ -52,6 +52,8 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.operation.preprocess.Preprocessors.maskLinks; @@ -240,6 +242,23 @@ public class RestAssuredRestDocumentationIntegrationTests { "http-response.adoc", "curl-request.adoc"); } + @Test + public void additionalSnippets() throws Exception { + RestDocumentationFilter documentation = document("{method-name}-{step}"); + RequestSpecification spec = new RequestSpecBuilder().setPort(this.port) + .addFilter(documentationConfiguration(this.restDocumentation)) + .addFilter(documentation).build(); + given(spec) + .filter(documentation + .document(responseHeaders(headerWithName("a").description("one"), + headerWithName("Foo").description("two")))) + .get("/").then().statusCode(200); + assertExpectedSnippetFilesExist( + new File("build/generated-snippets/additional-snippets-1/"), + "http-request.adoc", "http-response.adoc", "curl-request.adoc", + "response-headers.adoc"); + } + @Test public void preprocessedRequest() throws Exception { Pattern pattern = Pattern.compile("(\"alpha\")");