Improve testing of the generated documentation snippets

This commit is contained in:
Andy Wilkinson
2015-04-28 16:26:01 +01:00
parent 764daf7c99
commit da7efa3e3a
13 changed files with 780 additions and 345 deletions

View File

@@ -0,0 +1,37 @@
/*
* 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;
/**
* A {@link RuntimeException} thrown to indicate a problem with a RESTful resource's
* documentation.
*
* @author Andy Wilkinson
*/
@SuppressWarnings("serial")
public class RestDocumentationException extends RuntimeException {
/**
* Creates a new {@code RestDocumentationException} described by the given
* {@code message}
* @param message the message that describes the documentation problem
*/
public RestDocumentationException(String message) {
super(message);
}
}

View File

@@ -148,7 +148,10 @@ public abstract class HttpDocumentation {
}
}
this.writer.println();
this.writer.println(this.result.getResponse().getContentAsString());
String content = this.result.getResponse().getContentAsString();
if (StringUtils.hasText(content)) {
this.writer.println(content);
}
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.restdocs.hypermedia;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
@@ -26,6 +24,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.restdocs.RestDocumentationException;
import org.springframework.restdocs.snippet.DocumentationWriter;
import org.springframework.restdocs.snippet.DocumentationWriter.TableAction;
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
@@ -94,10 +93,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
+ undocumentedRels;
}
if (!missingRels.isEmpty()) {
if (message.length() > 0) {
message += ". ";
}
message += "Links with the following relations were not found in the response: "
+ missingRels;
}
fail(message);
throw new RestDocumentationException(message);
}
Assert.isTrue(actualRels.equals(expectedRels));

View File

@@ -121,7 +121,6 @@ class FieldProcessor {
.getParentMatch()))) {
return false;
}
;
}
return true;
}

View File

@@ -22,6 +22,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -51,14 +53,17 @@ class FieldValidator {
String message = "";
if (!undocumentedPayload.isEmpty()) {
message += String.format(
"Portions of the payload were not documented:%n%s",
"The following parts of the payload were not documented:%n%s",
this.objectMapper.writeValueAsString(undocumentedPayload));
}
if (!missingFields.isEmpty()) {
if (message.length() > 0) {
message += String.format("%n");
}
message += "Fields with the following paths were not found in the payload: "
+ missingFields;
}
throw new FieldValidationException(message);
throw new RestDocumentationException(message);
}
}
@@ -86,12 +91,4 @@ class FieldValidator {
return payload;
}
@SuppressWarnings("serial")
static class FieldValidationException extends RuntimeException {
FieldValidationException(String message) {
super(message);
}
}
}

View File

@@ -16,23 +16,19 @@
package org.springframework.restdocs.curl;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.junit.Assert.assertThat;
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest;
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
import static org.springframework.restdocs.test.StubMvcResult.result;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.StubMvcResult;
import org.springframework.restdocs.test.ExpectedSnippet;
/**
* Tests for {@link CurlDocumentation}
@@ -43,224 +39,174 @@ import org.springframework.restdocs.StubMvcResult;
*/
public class CurlDocumentationTests {
private final File outputDir = new File("build/curl-documentation-tests");
@Before
public void setup() {
System.setProperty("org.springframework.restdocs.outputDir",
this.outputDir.getAbsolutePath());
}
@After
public void cleanup() {
System.clearProperty("org.springframework.restdocs.outputDir");
}
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void getRequest() throws IOException {
documentCurlRequest("get-request").handle(
new StubMvcResult(new MockHttpServletRequest("GET", "/foo"), null));
assertThat(requestSnippetLines("get-request"),
hasItem("$ curl 'http://localhost/foo' -i"));
this.snippet.expectCurlRequest("get-request").withContents(
codeBlock("bash").content("$ curl 'http://localhost/foo' -i"));
documentCurlRequest("get-request").handle(result(get("/foo")));
}
@Test
public void nonGetRequest() throws IOException {
documentCurlRequest("non-get-request").handle(
new StubMvcResult(new MockHttpServletRequest("POST", "/foo"), null));
assertThat(requestSnippetLines("non-get-request"),
hasItem("$ curl 'http://localhost/foo' -i -X POST"));
this.snippet.expectCurlRequest("non-get-request").withContents(
codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
documentCurlRequest("non-get-request").handle(result(post("/foo")));
}
@Test
public void requestWithContent() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setContent("content".getBytes());
this.snippet.expectCurlRequest("request-with-content").withContents(
codeBlock("bash")
.content("$ curl 'http://localhost/foo' -i -d 'content'"));
documentCurlRequest("request-with-content").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-content"),
hasItem("$ curl 'http://localhost/foo' -i -d 'content'"));
}
@Test
public void requestWitUriQueryString() throws IOException {
documentCurlRequest("request-with-uri-query-string").handle(
new StubMvcResult(new MockHttpServletRequest("GET", "/foo?param=value"),
null));
assertThat(requestSnippetLines("request-with-uri-query-string"),
hasItem("$ curl 'http://localhost/foo?param=value' -i"));
result(get("/foo").content("content")));
}
@Test
public void requestWithQueryString() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setQueryString("param=value");
this.snippet.expectCurlRequest("request-with-query-string")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?param=value' -i"));
documentCurlRequest("request-with-query-string").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-query-string"),
hasItem("$ curl 'http://localhost/foo?param=value' -i"));
result(get("/foo?param=value")));
}
@Test
public void requestWithOneParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addParameter("k1", "v1");
this.snippet.expectCurlRequest("request-with-one-parameter").withContents(
codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i"));
documentCurlRequest("request-with-one-parameter").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-one-parameter"),
hasItem("$ curl 'http://localhost/foo?k1=v1' -i"));
result(get("/foo").param("k1", "v1")));
}
@Test
public void requestWithMultipleParameters() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addParameter("k1", "v1");
request.addParameter("k2", "v2");
request.addParameter("k1", "v1-bis");
this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i"));
documentCurlRequest("request-with-multiple-parameters").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-multiple-parameters"),
hasItem("$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i"));
result(get("/foo").param("k1", "v1").param("k2", "v2")
.param("k1", "v1-bis")));
}
@Test
public void requestWithUrlEncodedParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addParameter("k1", "foo bar&");
this.snippet.expectCurlRequest("request-with-url-encoded-parameter")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo?k1=foo+bar%26' -i"));
documentCurlRequest("request-with-url-encoded-parameter").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-url-encoded-parameter"),
hasItem("$ curl 'http://localhost/foo?k1=foo+bar%26' -i"));
result(get("/foo").param("k1", "foo bar&")));
}
@Test
public void postRequestWithOneParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
request.addParameter("k1", "v1");
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
documentCurlRequest("post-request-with-one-parameter").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("post-request-with-one-parameter"),
hasItem("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
result(post("/foo").param("k1", "v1")));
}
@Test
public void postRequestWithMultipleParameters() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
request.addParameter("k1", "v1");
request.addParameter("k2", "v2");
request.addParameter("k1", "v1-bis");
this.snippet.expectCurlRequest("post-request-with-multiple-parameters")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST"
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
documentCurlRequest("post-request-with-multiple-parameters").handle(
new StubMvcResult(request, null));
assertThat(
requestSnippetLines("post-request-with-multiple-parameters"),
hasItem("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1&k1=v1-bis&k2=v2'"));
result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2")));
}
@Test
public void postRequestWithUrlEncodedParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
request.addParameter("k1", "a&b");
this.snippet
.expectCurlRequest("post-request-with-url-encoded-parameter")
.withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
documentCurlRequest("post-request-with-url-encoded-parameter").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("post-request-with-url-encoded-parameter"),
hasItem("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
result(post("/foo").param("k1", "a&b")));
}
@Test
public void requestWithHeaders() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setContentType(MediaType.APPLICATION_JSON_VALUE);
request.addHeader("a", "alpha");
this.snippet.expectCurlRequest("request-with-headers").withContents(
codeBlock("bash").content(
"$ curl 'http://localhost/foo' -i"
+ " -H 'Content-Type: application/json' -H 'a: alpha'"));
documentCurlRequest("request-with-headers").handle(
new StubMvcResult(request, null));
assertThat(
requestSnippetLines("request-with-headers"),
hasItem("$ curl 'http://localhost/foo' -i -H 'Content-Type: application/json' -H 'a: alpha'"));
result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a",
"alpha")));
}
@Test
public void httpWithNonStandardPort() throws IOException {
this.snippet.expectCurlRequest("http-with-non-standard-port").withContents(
codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerPort(8080);
documentCurlRequest("http-with-non-standard-port").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("http-with-non-standard-port"),
hasItem("$ curl 'http://localhost:8080/foo' -i"));
documentCurlRequest("http-with-non-standard-port").handle(result(request));
}
@Test
public void httpsWithStandardPort() throws IOException {
this.snippet.expectCurlRequest("https-with-standard-port").withContents(
codeBlock("bash").content("$ curl 'https://localhost/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerPort(443);
request.setScheme("https");
documentCurlRequest("https-with-standard-port").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("https-with-standard-port"),
hasItem("$ curl 'https://localhost/foo' -i"));
documentCurlRequest("https-with-standard-port").handle(result(request));
}
@Test
public void httpsWithNonStandardPort() throws IOException {
this.snippet.expectCurlRequest("https-with-non-standard-port").withContents(
codeBlock("bash").content("$ curl 'https://localhost:8443/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerPort(8443);
request.setScheme("https");
documentCurlRequest("https-with-non-standard-port").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("https-with-non-standard-port"),
hasItem("$ curl 'https://localhost:8443/foo' -i"));
documentCurlRequest("https-with-non-standard-port").handle(result(request));
}
@Test
public void requestWithCustomHost() throws IOException {
this.snippet.expectCurlRequest("request-with-custom-host").withContents(
codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerName("api.example.com");
documentCurlRequest("request-with-custom-host").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-custom-host"),
hasItem("$ curl 'http://api.example.com/foo' -i"));
documentCurlRequest("request-with-custom-host").handle(result(request));
}
@Test
public void requestWithContextPathWithSlash() throws IOException {
this.snippet.expectCurlRequest("request-with-custom-context-with-slash")
.withContents(
codeBlock("bash").content(
"$ curl 'http://api.example.com/v3/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerName("api.example.com");
request.setContextPath("/v3");
documentCurlRequest("request-with-custom-context-with-slash").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-custom-context-with-slash"),
hasItem("$ curl 'http://api.example.com/v3/foo' -i"));
result(request));
}
@Test
public void requestWithContextPathWithoutSlash() throws IOException {
this.snippet.expectCurlRequest("request-with-custom-context-without-slash")
.withContents(
codeBlock("bash").content(
"$ curl 'http://api.example.com/v3/foo' -i"));
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setServerName("api.example.com");
request.setContextPath("v3");
documentCurlRequest("request-with-custom-context-without-slash").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("request-with-custom-context-without-slash"),
hasItem("$ curl 'http://api.example.com/v3/foo' -i"));
result(request));
}
private List<String> requestSnippetLines(String snippetName) throws IOException {
return snippetLines(snippetName, "curl-request");
}
private List<String> snippetLines(String snippetName, String snippetType)
throws IOException {
File snippetDir = new File(this.outputDir, snippetName);
File snippetFile = new File(snippetDir, snippetType + ".adoc");
String line = null;
List<String> lines = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(snippetFile));
try {
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
finally {
reader.close();
}
return lines;
}
}

View File

@@ -16,27 +16,25 @@
package org.springframework.restdocs.http;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.assertThat;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest;
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse;
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
import static org.springframework.restdocs.test.StubMvcResult.result;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.restdocs.StubMvcResult;
import org.springframework.restdocs.test.ExpectedSnippet;
/**
* Tests for {@link HttpDocumentation}
@@ -45,131 +43,93 @@ import org.springframework.restdocs.StubMvcResult;
*/
public class HttpDocumentationTests {
private final File outputDir = new File("build/http-documentation-tests");
@Before
public void setup() {
System.setProperty("org.springframework.restdocs.outputDir",
this.outputDir.getAbsolutePath());
}
@After
public void cleanup() {
System.clearProperty("org.springframework.restdocs.outputDir");
}
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void getRequest() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("Alpha", "a");
documentHttpRequest("get-request").handle(new StubMvcResult(request, null));
assertThat(requestSnippetLines("get-request"),
hasItems("GET /foo HTTP/1.1", "Alpha: a"));
this.snippet.expectHttpRequest("get-request").withContents(
httpRequest(GET, "/foo").header("Alpha", "a"));
documentHttpRequest("get-request").handle(
result(get("/foo").header("Alpha", "a")));
}
@Test
public void getRequestWithQueryString() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo?bar=baz");
this.snippet.expectHttpRequest("get-request-with-query-string").withContents(
httpRequest(GET, "/foo?bar=baz"));
documentHttpRequest("get-request-with-query-string").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("get-request-with-query-string"),
hasItems("GET /foo?bar=baz HTTP/1.1"));
result(get("/foo?bar=baz")));
}
@Test
public void getRequestWithParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addParameter("b&r", "baz");
this.snippet.expectHttpRequest("get-request-with-parameter").withContents(
httpRequest(GET, "/foo?b%26r=baz"));
documentHttpRequest("get-request-with-parameter").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("get-request-with-parameter"),
hasItems("GET /foo?b%26r=baz HTTP/1.1"));
result(get("/foo").param("b&r", "baz")));
}
@Test
public void postRequestWithContent() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
byte[] content = "Hello, world".getBytes();
request.setContent(content);
this.snippet.expectHttpRequest("post-request-with-content").withContents(
httpRequest(POST, "/foo") //
.content("Hello, world"));
documentHttpRequest("post-request-with-content").handle(
new StubMvcResult(request, null));
assertThat(requestSnippetLines("post-request-with-content"),
hasItems("POST /foo HTTP/1.1", "Hello, world"));
result(post("/foo").content("Hello, world")));
}
@Test
public void postRequestWithParameter() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
request.addParameter("b&r", "baz");
request.addParameter("a", "alpha");
this.snippet.expectHttpRequest("post-request-with-parameter").withContents(
httpRequest(POST, "/foo") //
.header("Content-Type", "application/x-www-form-urlencoded") //
.content("b%26r=baz&a=alpha"));
documentHttpRequest("post-request-with-parameter").handle(
new StubMvcResult(request, null));
assertThat(
requestSnippetLines("post-request-with-parameter"),
hasItems("POST /foo HTTP/1.1",
"Content-Type: application/x-www-form-urlencoded",
"b%26r=baz&a=alpha"));
result(post("/foo").param("b&r", "baz").param("a", "alpha")));
}
@Test
public void basicResponse() throws IOException {
documentHttpResponse("basic-response").handle(
new StubMvcResult(null, new MockHttpServletResponse()));
assertThat(responseSnippetLines("basic-response"), hasItem("HTTP/1.1 200 OK"));
this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK));
documentHttpResponse("basic-response").handle(result());
}
@Test
public void nonOkResponse() throws IOException {
this.snippet.expectHttpResponse("non-ok-response").withContents(
httpResponse(BAD_REQUEST));
MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(HttpStatus.BAD_REQUEST.value());
documentHttpResponse("non-ok-response").handle(new StubMvcResult(null, response));
assertThat(responseSnippetLines("non-ok-response"),
hasItem("HTTP/1.1 400 Bad Request"));
response.setStatus(BAD_REQUEST.value());
documentHttpResponse("non-ok-response").handle(result(response));
}
@Test
public void responseWithHeaders() throws IOException {
this.snippet.expectHttpResponse("response-with-headers").withContents(
httpResponse(OK) //
.header("Content-Type", "application/json") //
.header("a", "alpha"));
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setHeader("a", "alpha");
documentHttpResponse("non-ok-response").handle(new StubMvcResult(null, response));
assertThat(responseSnippetLines("non-ok-response"),
hasItems("HTTP/1.1 200 OK", "Content-Type: application/json", "a: alpha"));
documentHttpResponse("response-with-headers").handle(result(response));
}
@Test
public void responseWithContent() throws IOException {
this.snippet.expectHttpResponse("response-with-content").withContents(
httpResponse(OK).content("content"));
MockHttpServletResponse response = new MockHttpServletResponse();
response.getWriter().append("content");
documentHttpResponse("response-with-content").handle(
new StubMvcResult(null, response));
assertThat(responseSnippetLines("response-with-content"),
hasItems("HTTP/1.1 200 OK", "content"));
documentHttpResponse("response-with-content").handle(result(response));
}
private List<String> requestSnippetLines(String snippetName) throws IOException {
return snippetLines(snippetName, "http-request");
}
private List<String> responseSnippetLines(String snippetName) throws IOException {
return snippetLines(snippetName, "http-response");
}
private List<String> snippetLines(String snippetName, String snippetType)
throws IOException {
File snippetDir = new File(this.outputDir, snippetName);
File snippetFile = new File(snippetDir, snippetType + ".adoc");
String line = null;
List<String> lines = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(snippetFile));
try {
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
finally {
reader.close();
}
return lines;
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.hypermedia;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks;
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
import static org.springframework.restdocs.test.StubMvcResult.result;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.restdocs.RestDocumentationException;
import org.springframework.restdocs.test.ExpectedSnippet;
/**
* Tests for {@link HypermediaDocumentation}
*
* @author Andy Wilkinson
*/
public class HypermediaDocumentationTests {
@Rule
public ExpectedSnippet snippet = new ExpectedSnippet();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void undocumentedLink() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " documented: [foo]"));
documentLinks("undocumented-link",
new StubLinkExtractor().withLinks(new Link("foo", "bar"))).handle(
result());
}
@Test
public void missingLink() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " found in the response: [foo]"));
documentLinks("undocumented-link", new StubLinkExtractor(),
new LinkDescriptor("foo").description("bar")).handle(result());
}
@Test
public void undocumentedLinkAndMissingLink() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown.expectMessage(equalTo("Links with the following relations were not"
+ " documented: [a]. Links with the following relations were not"
+ " found in the response: [foo]"));
documentLinks("undocumented-link-and-missing-link",
new StubLinkExtractor().withLinks(new Link("a", "alpha")),
new LinkDescriptor("foo").description("bar")).handle(result());
}
@Test
public void documentedLinks() throws IOException {
this.snippet.expectLinks("documented-links").withContents( //
tableWithHeader("Relation", "Description") //
.row("a", "one") //
.row("b", "two"));
documentLinks(
"documented-links",
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
"bravo")), new LinkDescriptor("a").description("one"),
new LinkDescriptor("b").description("two")).handle(result());
}
private static class StubLinkExtractor implements LinkExtractor {
private Map<String, List<Link>> linksByRel = new HashMap<String, List<Link>>();
@Override
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
throws IOException {
return this.linksByRel;
}
private StubLinkExtractor withLinks(Link... links) {
for (Link link : links) {
List<Link> linksWithRel = this.linksByRel.get(link.getRel());
if (linksWithRel == null) {
linksWithRel = new ArrayList<Link>();
this.linksByRel.put(link.getRel(), linksWithRel);
}
linksWithRel.add(link);
}
return this;
}
}
}

View File

@@ -24,7 +24,7 @@ import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.restdocs.payload.FieldValidator.FieldValidationException;
import org.springframework.restdocs.RestDocumentationException;
/**
* Tests for {@link FieldValidator}
@@ -70,9 +70,10 @@ public class FieldValidatorTests {
@Test
public void missingField() throws IOException {
this.thrownException.expect(FieldValidationException.class);
this.thrownException.expect(RestDocumentationException.class);
this.thrownException
.expectMessage(equalTo("Fields with the following paths were not found in the payload: [y, z]"));
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [y, z]"));
this.fieldValidator.validate(this.payload, Arrays.asList(
new FieldDescriptor("a"), new FieldDescriptor("a.b"),
new FieldDescriptor("y"), new FieldDescriptor("z")));
@@ -80,10 +81,10 @@ public class FieldValidatorTests {
@Test
public void undocumentedField() throws IOException {
this.thrownException.expect(FieldValidationException.class);
this.thrownException
.expectMessage(equalTo(String
.format("Portions of the payload were not documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}")));
this.thrownException.expect(RestDocumentationException.class);
this.thrownException.expectMessage(equalTo(String
.format("The following parts of the payload were not"
+ " documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}")));
this.fieldValidator.validate(this.payload,
Arrays.asList(new FieldDescriptor("a.b"), new FieldDescriptor("a.d")));
}

View File

@@ -15,31 +15,24 @@
*/
package org.springframework.restdocs.payload;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
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.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.junit.rules.ExpectedException;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.restdocs.StubMvcResult;
import org.springframework.util.StringUtils;
import org.springframework.restdocs.RestDocumentationException;
import org.springframework.restdocs.test.ExpectedSnippet;
/**
* Tests for {@link PayloadDocumentation}
@@ -48,40 +41,42 @@ import org.springframework.util.StringUtils;
*/
public class PayloadDocumentationTests {
private final File outputDir = new File("build/payload-documentation-tests");
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Before
public void setup() {
System.setProperty("org.springframework.restdocs.outputDir",
this.outputDir.getAbsolutePath());
}
@After
public void cleanup() {
System.clearProperty("org.springframework.restdocs.outputDir");
}
@Rule
public final ExpectedSnippet snippet = new ExpectedSnippet();
@Test
public void requestWithFields() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes());
this.snippet.expectRequestFields("request-with-fields").withContents( //
tableWithHeader("Path", "Type", "Description") //
.row("a.b", "Number", "one") //
.row("a.c", "String", "two") //
.row("a", "Object", "three"));
documentRequestFields("request-with-fields",
fieldWithPath("a.b").description("one"),
fieldWithPath("a.c").description("two"),
fieldWithPath("a").description("three")).handle(
new StubMvcResult(request, null));
assertThat(
snippet("request-with-fields", "request-fields"),
is(asciidoctorTableWith(header("Path", "Type", "Description"),
row("a.b", "Number", "one"), row("a.c", "String", "two"),
row("a", "Object", "three"))));
result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")));
}
@Test
public void responseWithFields() throws IOException {
this.snippet.expectResponseFields("response-with-fields").withContents(//
tableWithHeader("Path", "Type", "Description") //
.row("id", "Number", "one") //
.row("date", "String", "two") //
.row("assets", "Array", "three") //
.row("assets[]", "Object", "four") //
.row("assets[].id", "Number", "five") //
.row("assets[].name", "String", "six"));
MockHttpServletResponse response = new MockHttpServletResponse();
response.getWriter()
.append("{\"id\": 67,\"date\": \"2015-01-20\",\"assets\": [{\"id\":356,\"name\": \"sample\"}]}");
response.getWriter().append(
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
+ " [{\"id\":356,\"name\": \"sample\"}]}");
documentResponseFields("response-with-fields",
fieldWithPath("id").description("one"),
fieldWithPath("date").description("two"),
@@ -89,67 +84,41 @@ public class PayloadDocumentationTests {
fieldWithPath("assets[]").description("four"),
fieldWithPath("assets[].id").description("five"),
fieldWithPath("assets[].name").description("six")).handle(
new StubMvcResult(new MockHttpServletRequest("GET", "/"), response));
assertThat(
snippet("response-with-fields", "response-fields"),
is(asciidoctorTableWith(header("Path", "Type", "Description"),
row("id", "Number", "one"), row("date", "String", "two"),
row("assets", "Array", "three"),
row("assets[]", "Object", "four"),
row("assets[].id", "Number", "five"),
row("assets[].name", "String", "six"))));
result(response));
}
private Matcher<Iterable<? extends String>> asciidoctorTableWith(String[] header,
String[]... rows) {
Collection<Matcher<? super String>> matchers = new ArrayList<Matcher<? super String>>();
for (String headerItem : header) {
matchers.add(equalTo(headerItem));
}
for (String[] row : rows) {
for (String rowItem : row) {
matchers.add(equalTo(rowItem));
}
}
matchers.add(equalTo("|==="));
matchers.add(equalTo(""));
return new IsIterableContainingInAnyOrder<String>(matchers);
@Test
public void undocumentedRequestField() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
documentRequestFields("undocumented-request-fields").handle(
result(get("/foo").content("{\"a\": 5}")));
}
private String[] header(String... columns) {
String header = "|"
+ StringUtils.collectionToDelimitedString(Arrays.asList(columns), "|");
return new String[] { "", "|===", header, "" };
@Test
public void missingRequestField() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown
.expectMessage(equalTo("Fields with the following paths were not found"
+ " in the payload: [a.b]"));
documentRequestFields("missing-request-fields",
fieldWithPath("a.b").description("one")).handle(
result(get("/foo").content("{}")));
}
private String[] row(String... entries) {
List<String> lines = new ArrayList<String>();
for (String entry : entries) {
lines.add("|" + entry);
}
lines.add("");
return lines.toArray(new String[lines.size()]);
@Test
public void undocumentedRequestFieldAndMissingRequestField() throws IOException {
this.thrown.expect(RestDocumentationException.class);
this.thrown
.expectMessage(startsWith("The following parts of the payload were not"
+ " documented:"));
this.thrown
.expectMessage(endsWith("Fields with the following paths were not found"
+ " in the payload: [a.b]"));
documentRequestFields("undocumented-request-field-and-missing-request-field",
fieldWithPath("a.b").description("one")).handle(
result(get("/foo").content("{ \"a\": { \"c\": 5 }}")));
}
private List<String> snippet(String snippetName, String snippetType)
throws IOException {
File snippetDir = new File(this.outputDir, snippetName);
File snippetFile = new File(snippetDir, snippetType + ".adoc");
String line = null;
List<String> lines = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(snippetFile));
try {
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
finally {
reader.close();
}
return lines;
}
}

View File

@@ -0,0 +1,193 @@
/*
* 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.test;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* The {@code ExpectedSnippet} rule is used to verify that a
* {@link SnippetWritingResultHandler} has generated the expected snippet.
*
* @author Andy Wilkinson
*/
public class ExpectedSnippet implements TestRule {
private String expectedName;
private String expectedType;
private Matcher<String> expectedContents;
private File outputDir;
@Override
public Statement apply(final Statement base, Description description) {
this.outputDir = new File("build/" + description.getTestClass().getSimpleName());
return new OutputDirectoryStatement(new ExpectedSnippetStatement(base),
this.outputDir);
}
private static final class OutputDirectoryStatement extends Statement {
private final Statement delegate;
private final File outputDir;
public OutputDirectoryStatement(Statement delegate, File outputDir) {
this.delegate = delegate;
this.outputDir = outputDir;
}
@Override
public void evaluate() throws Throwable {
System.setProperty("org.springframework.restdocs.outputDir",
this.outputDir.getAbsolutePath());
try {
this.delegate.evaluate();
}
finally {
System.clearProperty("org.springframework.restdocs.outputDir");
}
}
}
private final class ExpectedSnippetStatement extends Statement {
private final Statement delegate;
public ExpectedSnippetStatement(Statement delegate) {
this.delegate = delegate;
}
@Override
public void evaluate() throws Throwable {
this.delegate.evaluate();
verifySnippet();
}
}
private void verifySnippet() throws IOException {
if (this.outputDir != null && this.expectedName != null) {
File snippetDir = new File(this.outputDir, this.expectedName);
File snippetFile = new File(snippetDir, this.expectedType + ".adoc");
assertTrue("The file " + snippetFile + " does not exist or is not a file",
snippetFile.isFile());
if (this.expectedContents != null) {
assertThat(read(snippetFile), this.expectedContents);
}
}
}
public ExpectedSnippet expectCurlRequest(String name) {
expect(name, "curl-request");
return this;
}
public ExpectedSnippet expectRequestFields(String name) {
expect(name, "request-fields");
return this;
}
public ExpectedSnippet expectResponseFields(String name) {
expect(name, "response-fields");
return this;
}
public ExpectedSnippet expectLinks(String name) {
expect(name, "links");
return this;
}
public ExpectedSnippet expectHttpRequest(String name) {
expect(name, "http-request");
return this;
}
public ExpectedSnippet expectHttpResponse(String name) {
expect(name, "http-response");
return this;
}
private ExpectedSnippet expect(String name, String type) {
this.expectedName = name;
this.expectedType = type;
return this;
}
public void withContents(Matcher<String> matcher) {
this.expectedContents = matcher;
}
private String read(File snippetFile) throws IOException {
return FileCopyUtils.copyToString(new FileReader(snippetFile));
}
public Matcher<Iterable<? extends String>> asciidoctorTableWith(String[] header,
String[]... rows) {
Collection<Matcher<? super String>> matchers = new ArrayList<Matcher<? super String>>();
for (String headerItem : header) {
matchers.add(equalTo(headerItem));
}
for (String[] row : rows) {
for (String rowItem : row) {
matchers.add(equalTo(rowItem));
}
}
matchers.add(equalTo("|==="));
matchers.add(equalTo(""));
return new IsIterableContainingInAnyOrder<String>(matchers);
}
public String[] header(String... columns) {
String header = "|"
+ StringUtils.collectionToDelimitedString(Arrays.asList(columns), "|");
return new String[] { "", "|===", header, "" };
}
public String[] row(String... entries) {
List<String> lines = new ArrayList<String>();
for (String entry : entries) {
lines.add("|" + entry);
}
lines.add("");
return lines.toArray(new String[lines.size()]);
}
}

View File

@@ -0,0 +1,177 @@
/*
* 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.test;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* {@link Matcher Matchers} for verify the contents of generated documentation snippets.
*
* @author Andy Wilkinson
*/
public class SnippetMatchers {
public static AsciidoctorTableMatcher tableWithHeader(String... headers) {
return new AsciidoctorTableMatcher(headers);
}
public static HttpRequestMatcher httpRequest(RequestMethod method, String uri) {
return new HttpRequestMatcher(method, uri);
}
public static HttpResponseMatcher httpResponse(HttpStatus status) {
return new HttpResponseMatcher(status);
}
@SuppressWarnings({ "rawtypes" })
public static AsciidoctorCodeBlockMatcher<?> codeBlock(String language) {
return new AsciidoctorCodeBlockMatcher(language);
}
private static abstract class AbstractSnippetMatcher extends BaseMatcher<String> {
private List<String> lines = new ArrayList<String>();
protected void addLine(String line) {
this.lines.add(line);
}
protected void addLine(int index, String line) {
if (index < 0) {
index = index + this.lines.size();
}
this.lines.add(index, line);
}
@Override
public boolean matches(Object item) {
return getLinesAsString().equals(item);
}
@Override
public void describeTo(Description description) {
description.appendText("Asciidoctor snippet");
description.appendText(getLinesAsString());
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("was:");
if (item instanceof String) {
description.appendText((String) item);
}
else {
description.appendValue(item);
}
}
private String getLinesAsString() {
StringWriter writer = new StringWriter();
for (String line : this.lines) {
writer.append(String.format("%s%n", line));
}
return writer.toString();
}
}
public static class AsciidoctorCodeBlockMatcher<T extends AsciidoctorCodeBlockMatcher<T>>
extends AbstractSnippetMatcher {
protected AsciidoctorCodeBlockMatcher(String language) {
this.addLine("");
this.addLine("[source," + language + "]");
this.addLine("----");
this.addLine("----");
this.addLine("");
}
@SuppressWarnings("unchecked")
public T content(String content) {
this.addLine(-2, content);
return (T) this;
}
}
public static abstract class HttpMatcher<T extends HttpMatcher<T>> extends
AsciidoctorCodeBlockMatcher<HttpMatcher<T>> {
private int headerOffset = 4;
protected HttpMatcher() {
super("http");
}
@SuppressWarnings("unchecked")
public T header(String name, String value) {
this.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
}
public static class HttpResponseMatcher extends HttpMatcher<HttpResponseMatcher> {
public HttpResponseMatcher(HttpStatus status) {
this.content("HTTP/1.1 " + status.value() + " " + status.getReasonPhrase());
this.content("");
}
}
public static class HttpRequestMatcher extends HttpMatcher<HttpRequestMatcher> {
public HttpRequestMatcher(RequestMethod requestMethod, String uri) {
this.content(requestMethod.name() + " " + uri + " HTTP/1.1");
this.content("");
}
}
public static class AsciidoctorTableMatcher extends AbstractSnippetMatcher {
private AsciidoctorTableMatcher(String... columns) {
this.addLine("");
this.addLine("|===");
String header = "|"
+ StringUtils
.collectionToDelimitedString(Arrays.asList(columns), "|");
this.addLine(header);
this.addLine("");
this.addLine("|===");
this.addLine("");
}
public AsciidoctorTableMatcher row(String... entries) {
for (String entry : entries) {
this.addLine(-2, "|" + entry);
}
this.addLine(-2, "");
return this;
}
}
}

View File

@@ -14,11 +14,13 @@
* limitations under the License.
*/
package org.springframework.restdocs;
package org.springframework.restdocs.test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@@ -35,11 +37,43 @@ public class StubMvcResult implements MvcResult {
private final MockHttpServletResponse response;
public StubMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) {
public static StubMvcResult result() {
return new StubMvcResult();
}
public static StubMvcResult result(RequestBuilder requestBuilder) {
return new StubMvcResult(requestBuilder);
}
public static StubMvcResult result(MockHttpServletRequest request) {
return new StubMvcResult(request);
}
public static StubMvcResult result(MockHttpServletResponse response) {
return new StubMvcResult(response);
}
private StubMvcResult() {
this(new MockHttpServletRequest(), new MockHttpServletResponse());
}
private StubMvcResult(MockHttpServletRequest request) {
this(request, new MockHttpServletResponse());
}
private StubMvcResult(MockHttpServletResponse response) {
this(new MockHttpServletRequest(), response);
}
private StubMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) {
this.request = request;
this.response = response;
}
private StubMvcResult(RequestBuilder requestBuilder) {
this(requestBuilder.buildRequest(new MockServletContext()));
}
@Override
public MockHttpServletRequest getRequest() {
return this.request;