Add support for documenting a request’s query parameters

This commit adds support for documenting a request's query parameters.
For example:

mockMvc.perform(
        get("/issues").param("page", "2").param("limit", "20"))
                .andDo(documentQueryParameters("list-issues",
                        parameterWithName("page")
                                .description("Page number to return"),
                        parameterWithName("limit")
                                .description("Maximum page size")));

Closes gh-39
This commit is contained in:
Andy Wilkinson
2015-04-29 09:22:33 +01:00
parent b362c493ff
commit b3eee08b5d
6 changed files with 348 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRe
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks;
import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields;
import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters;
import java.util.ArrayList;
import java.util.List;
@@ -32,6 +33,8 @@ import org.springframework.restdocs.hypermedia.LinkExtractor;
import org.springframework.restdocs.hypermedia.LinkExtractors;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.restdocs.request.ParameterDescriptor;
import org.springframework.restdocs.request.RequestDocumentation;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
@@ -138,4 +141,22 @@ public class RestDocumentationResultHandler implements ResultHandler {
return this;
}
/**
* Documents the parameters in the request's query string using the given
* {@code descriptors}.
* <p>
* If a parameter is present in the query string but is not described by one of the
* descriptors a failure will occur when this handler is invoked. Similarly, if a
* parameter is described but is not present in the request a failure will also occur
* when this handler is invoked.
*
* @param descriptors the parameter descriptors
* @return {@code this}
* @see RequestDocumentation#parameterWithName(String)
*/
public RestDocumentationResultHandler withQueryParameters(
ParameterDescriptor... descriptors) {
this.delegates.add(documentQueryParameters(this.outputDir, descriptors));
return this;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.request;
/**
* A descriptor of a parameter in a query string
*
* @author Andy Wilkinson
* @see RequestDocumentation#parameterWithName
*
*/
public class ParameterDescriptor {
private final String name;
private String description;
ParameterDescriptor(String name) {
this.name = name;
}
/**
* Specifies the description of the parameter
*
* @param description The parameter's description
* @return {@code this}
*/
public ParameterDescriptor description(String description) {
this.description = description;
return this;
}
String getName() {
return this.name;
}
String getDescription() {
return this.description;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.request;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.restdocs.snippet.DocumentationWriter;
import org.springframework.restdocs.snippet.DocumentationWriter.TableAction;
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
import org.springframework.restdocs.snippet.SnippetGenerationException;
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.Assert;
/**
* A {@link SnippetWritingResultHandler} that produces a snippet documenting the query
* parameters supported by a RESTful resource.
*
* @author Andy Wilkinson
*/
public class QueryParametersSnippetResultHandler extends SnippetWritingResultHandler {
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
protected QueryParametersSnippetResultHandler(String outputDir,
ParameterDescriptor... descriptors) {
super(outputDir, "query-parameters");
for (ParameterDescriptor descriptor : descriptors) {
Assert.hasText(descriptor.getName());
Assert.hasText(descriptor.getDescription());
this.descriptorsByName.put(descriptor.getName(), descriptor);
}
}
@Override
protected void handle(MvcResult result, DocumentationWriter writer)
throws IOException {
verifyParameterDescriptors(result);
documentParameters(writer);
}
private void verifyParameterDescriptors(MvcResult result) {
Set<String> actualParameters = result.getRequest().getParameterMap().keySet();
Set<String> expectedParameters = this.descriptorsByName.keySet();
Set<String> undocumentedParameters = new HashSet<String>(actualParameters);
undocumentedParameters.removeAll(expectedParameters);
Set<String> missingParameters = new HashSet<String>(expectedParameters);
missingParameters.removeAll(actualParameters);
if (!undocumentedParameters.isEmpty() || !missingParameters.isEmpty()) {
String message = "";
if (!undocumentedParameters.isEmpty()) {
message += "Query parameters with the following names were not documented: "
+ undocumentedParameters;
}
if (!missingParameters.isEmpty()) {
if (message.length() > 0) {
message += ". ";
}
message += "Query parameters with the following names were not found in the request: "
+ missingParameters;
}
throw new SnippetGenerationException(message);
}
Assert.isTrue(actualParameters.equals(expectedParameters));
}
private void documentParameters(DocumentationWriter writer) throws IOException {
writer.table(new TableAction() {
@Override
public void perform(TableWriter tableWriter) throws IOException {
tableWriter.headers("Parameter", "Description");
for (Entry<String, ParameterDescriptor> entry : QueryParametersSnippetResultHandler.this.descriptorsByName
.entrySet()) {
tableWriter.row(entry.getKey(), entry.getValue().getDescription());
}
}
});
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.request;
import org.springframework.restdocs.RestDocumentationResultHandler;
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
/**
* Static factory methods for documenting aspects of a request sent to a RESTful API.
*
* @author Andy Wilkinson
*/
public abstract class RequestDocumentation {
private RequestDocumentation() {
}
/**
* Creates a {@link SnippetWritingResultHandler} that will produce a snippet
* documenting a request's query parameters
*
* @param outputDir The directory to which the snippet should be written
* @param descriptors The descriptions of the parameters in the request's query string
* @return the result handler
* @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...)
*/
public static SnippetWritingResultHandler documentQueryParameters(String outputDir,
ParameterDescriptor... descriptors) {
return new QueryParametersSnippetResultHandler(outputDir, descriptors);
}
/**
* Creates a {@link ParameterDescriptor} that describes a query string parameter with
* the given {@code name}.
*
* @param name The name of the parameter
* @return a {@link ParameterDescriptor} ready for further configuration
* @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...)
*/
public static ParameterDescriptor parameterWithName(String name) {
return new ParameterDescriptor(name);
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.request;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import static org.springframework.restdocs.test.StubMvcResult.result;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.restdocs.snippet.SnippetGenerationException;
import org.springframework.restdocs.test.ExpectedSnippet;
/**
* Requests for {@link RequestDocumentation}
*
* @author Andy Wilkinson
*/
public class RequestDocumentationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void undocumentedParameter() throws IOException {
this.thrown.expect(SnippetGenerationException.class);
this.thrown
.expectMessage(equalTo("Query parameters with the following names were"
+ " not documented: [a]"));
documentQueryParameters("undocumented-parameter").handle(
result(get("/").param("a", "alpha")));
}
@Test
public void missingParameter() throws IOException {
this.thrown.expect(SnippetGenerationException.class);
this.thrown
.expectMessage(equalTo("Query parameters with the following names were"
+ " not found in the request: [a]"));
documentQueryParameters("missing-parameter",
parameterWithName("a").description("one")).handle(result(get("/")));
}
@Test
public void undocumentedParameterAndMissingParameter() throws IOException {
this.thrown.expect(SnippetGenerationException.class);
this.thrown
.expectMessage(equalTo("Query parameters with the following names were"
+ " not documented: [b]. Query parameters with the following"
+ " names were not found in the request: [a]"));
documentQueryParameters("undocumented-parameter-missing-parameter",
parameterWithName("a").description("one")).handle(
result(get("/").param("b", "bravo")));
}
@Test
public void parameterSnippetFromRequestParameters() throws IOException {
this.snippet.expectQueryParameters("parameter-snippet-request-parameters")
.withContents(
tableWithHeader("Parameter", "Description").row("a", "one").row(
"b", "two"));
documentQueryParameters("parameter-snippet-request-parameters",
parameterWithName("a").description("one"),
parameterWithName("b").description("two")).handle(
result(get("/").param("a", "bravo").param("b", "bravo")));
}
@Test
public void parameterSnippetFromRequestUriQueryString() throws IOException {
this.snippet.expectQueryParameters("parameter-snippet-request-uri-query-string")
.withContents(
tableWithHeader("Parameter", "Description").row("a", "one").row(
"b", "two"));
documentQueryParameters("parameter-snippet-request-uri-query-string",
parameterWithName("a").description("one"),
parameterWithName("b").description("two")).handle(
result(get("/?a=alpha&b=bravo")));
}
}

View File

@@ -142,6 +142,11 @@ public class ExpectedSnippet implements TestRule {
return this;
}
public ExpectedSnippet expectQueryParameters(String name) {
expect(name, "query-parameters");
return this;
}
private ExpectedSnippet expect(String name, String type) {
this.expectedName = name;
this.expectedType = type;