Remove the need for manual documentCurlRequest/Response calls
Previously, whenever a MockMvc perform call was made and the request and response were to be documented, calls to documentCurlRequest and documentCurlResponse had to be made. This commit updates the REST documentation framework to make the documentation of the request and response automatic. It makes use of MockMvc’s MockMvcConfigurer that was introduced in Spring 4.1. Applying RestDocumentationConfiguration once will cause all requests and responses to be documented: this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfiguration()).build(); The path to which the requests and response documentation snippets will be written is determined by the class and method in which the MockMvc perform call is made and is currently of the form shortClassName/methodName(Request|Response|RequestResponse).asciidoc where shortClassName is the name of the documentation class without its package. This works for both @Test methods and any non-private methods that are called from the test method. The methods must be non-private as a CGLib proxy is used to intercept the calls and configure the output path.
This commit is contained in:
@@ -42,12 +42,12 @@ $ java -jar build/libs/*.jar
|
||||
You can check that the service is up and running by executing a simple request using
|
||||
cURL:
|
||||
|
||||
include::{generated}/index/access_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/indexRequest.asciidoc[]
|
||||
|
||||
This request should yield the following response in the
|
||||
http://stateless.co/hal_specification.html[Hypertext Application Language (HAL)] format:
|
||||
|
||||
include::{generated}/index/access_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[]
|
||||
|
||||
Note the `_links` in the JSON response. They are key to navigating the API.
|
||||
|
||||
@@ -59,26 +59,26 @@ Now that you've started the service and verified that it works, the next step is
|
||||
it to create a new note. As you saw above, the URI for working with notes is included as
|
||||
a link when you perform a `GET` request against the root of the service:
|
||||
|
||||
include::{generated}/index/access_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[]
|
||||
|
||||
To create a note, you need to execute a `POST` request to this URI including a JSON
|
||||
payload containing the title and body of the note:
|
||||
|
||||
include::{generated}/notes/create_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createNoteRequest.asciidoc[]
|
||||
|
||||
The response from this request should have a status code of `201 Created` and contain a
|
||||
`Location` header whose value is the URI of the newly created note:
|
||||
|
||||
include::{generated}/notes/create_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[]
|
||||
|
||||
To work with the newly created note you use the URI in the `Location` header. For example,
|
||||
you can access the note's details by performing a `GET` request:
|
||||
|
||||
include::{generated}/notes/get_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getNoteRequest.asciidoc[]
|
||||
|
||||
This request will produce a response with the note's details in its body:
|
||||
|
||||
include::{generated}/notes/get_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getNoteResponse.asciidoc[]
|
||||
|
||||
Note the `tags` link which we'll make use of later.
|
||||
|
||||
@@ -92,26 +92,26 @@ to tag a note, you must first create the tag.
|
||||
Referring back to the response for the service's index, the URI for working with tags is
|
||||
include as a link:
|
||||
|
||||
include::{generated}/index/access_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[]
|
||||
|
||||
To create a tag you need to execute a `POST` request to this URI, including a JSON
|
||||
payload containing the name of the tag:
|
||||
|
||||
include::{generated}/tags/create_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createTagRequest.asciidoc[]
|
||||
|
||||
The response from this request should have a status code of `201 Created` and contain a
|
||||
`Location` header whose value is the URI of the newly created tag:
|
||||
|
||||
include::{generated}/tags/create_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createTagResponse.asciidoc[]
|
||||
|
||||
To work with the newly created tag you use the URI in the `Location` header. For example
|
||||
you can access the tag's details by performing a `GET` request:
|
||||
|
||||
include::{generated}/tags/get_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTagRequest.asciidoc[]
|
||||
|
||||
This request will produce a response with the tag's details in its body:
|
||||
|
||||
include::{generated}/tags/get_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTagResponse.asciidoc[]
|
||||
|
||||
|
||||
|
||||
@@ -132,24 +132,24 @@ with it.
|
||||
Once again we execute a `POST` request. However, this time, in an array named tags, we
|
||||
include the URI of the tag we just created:
|
||||
|
||||
include::{generated}/notes/create_tagged_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createTaggedNoteRequest.asciidoc[]
|
||||
|
||||
Once again, the response's `Location` header tells us the URI of the newly created note:
|
||||
|
||||
include::{generated}/notes/create_tagged_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createTaggedNoteResponse.asciidoc[]
|
||||
|
||||
As before, a `GET` request executed against this URI will retrieve the note's details:
|
||||
|
||||
include::{generated}/notes/get_tagged_with_curl.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTaggedNoteRequestResponse.asciidoc[]
|
||||
|
||||
To verify that the tag has been associated with the note, we can perform a `GET` request
|
||||
against the URI from the `tags` link:
|
||||
|
||||
include::{generated}/notes/get_tags_for_tagged_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTagsRequest.asciidoc[]
|
||||
|
||||
The response embeds information about the tag that we've just associated with the note:
|
||||
|
||||
include::{generated}/notes/get_tags_for_tagged_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTagsResponse.asciidoc[]
|
||||
|
||||
|
||||
|
||||
@@ -159,17 +159,17 @@ An existing note can be tagged by executing a `PATCH` request against the note's
|
||||
a body that contains the array of tags to be associated with the note. We'll used the
|
||||
URI of the untagged note that we created earlier:
|
||||
|
||||
include::{generated}/notes/tag_existing_with_curl_request.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/tagExistingNoteRequest.asciidoc[]
|
||||
|
||||
This request should produce a `204 No Content` response:
|
||||
|
||||
include::{generated}/notes/tag_existing_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/tagExistingNoteResponse.asciidoc[]
|
||||
|
||||
When we first created this note, we noted the tags link included in its details:
|
||||
|
||||
include::{generated}/notes/get_with_curl_response.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[]
|
||||
|
||||
We can use that link now and execute a `GET` request to see that the note now has a
|
||||
single tag:
|
||||
|
||||
include::{generated}/notes/get_tags_for_existing_with_curl.asciidoc[]
|
||||
include::{generated}/GettingStartedDocumentation/getTagsForExistingNoteRequestResponse.asciidoc[]
|
||||
|
||||
@@ -19,14 +19,9 @@ package com.example.notes;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.core.RestDocumentationRequestPostProcessors.port;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequest;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequestAndResponse;
|
||||
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlResponse;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -42,7 +37,8 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.restdocs.core.RestDocumentationConfiguration;
|
||||
import org.springframework.restdocs.core.RestDocumentationJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -53,7 +49,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@RunWith(RestDocumentationJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = Application.class)
|
||||
@WebAppConfiguration
|
||||
public class GettingStartedDocumentation {
|
||||
@@ -68,17 +64,16 @@ public class GettingStartedDocumentation {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfiguration()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void index() throws Exception {
|
||||
this.mockMvc.perform(get("/").with(port(8080)).accept(MediaTypes.HAL_JSON))
|
||||
this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
||||
.andDo(documentCurlRequest("index/access_with_curl_request.asciidoc"))
|
||||
.andDo(documentCurlResponse("index/access_with_curl_response.asciidoc"));
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,63 +93,49 @@ public class GettingStartedDocumentation {
|
||||
getTagsForExistingNote(noteLocation);
|
||||
}
|
||||
|
||||
private String createNote() throws Exception {
|
||||
String createNote() throws Exception {
|
||||
Map<String, String> note = new HashMap<String, String>();
|
||||
note.put("title", "Note creation with cURL");
|
||||
note.put("body", "An example of how to create a note using cURL");
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").with(port(8080)).contentType(MediaTypes.HAL_JSON)
|
||||
.content(objectMapper.writeValueAsString(note)))
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().string("Location", notNullValue()))
|
||||
.andDo(documentCurlRequest("notes/create_with_curl_request.asciidoc")
|
||||
.includeResponseHeaders())
|
||||
.andDo(documentCurlResponse("notes/create_with_curl_response.asciidoc")
|
||||
.includeResponseHeaders()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
.andExpect(header().string("Location", notNullValue())).andReturn()
|
||||
.getResponse().getHeader("Location");
|
||||
return noteLocation;
|
||||
}
|
||||
|
||||
private void getNote(String noteLocation) throws Exception {
|
||||
this.mockMvc.perform(get(noteLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
void getNote(String noteLocation) throws Exception {
|
||||
this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(notNullValue())))
|
||||
.andExpect(jsonPath("body", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
||||
.andDo(documentCurlRequest("notes/get_with_curl_request.asciidoc"))
|
||||
.andDo(documentCurlResponse("notes/get_with_curl_response.asciidoc"));
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
}
|
||||
|
||||
private String createTag() throws Exception, JsonProcessingException {
|
||||
String createTag() throws Exception, JsonProcessingException {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "getting-started");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").with(port(8080)).contentType(MediaTypes.HAL_JSON)
|
||||
.content(objectMapper.writeValueAsString(tag)))
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().string("Location", notNullValue()))
|
||||
.andDo(documentCurlRequest("tags/create_with_curl_request.asciidoc")
|
||||
.includeResponseHeaders())
|
||||
.andDo(documentCurlResponse("tags/create_with_curl_response.asciidoc")
|
||||
.includeResponseHeaders()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
.andExpect(header().string("Location", notNullValue())).andReturn()
|
||||
.getResponse().getHeader("Location");
|
||||
return tagLocation;
|
||||
}
|
||||
|
||||
private void getTag(String tagLocation) throws Exception {
|
||||
this.mockMvc.perform(get(tagLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
void getTag(String tagLocation) throws Exception {
|
||||
this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())))
|
||||
.andDo(documentCurlRequest("tags/get_with_curl_request.asciidoc"))
|
||||
.andDo(documentCurlResponse("tags/get_with_curl_response.asciidoc"));
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())));
|
||||
}
|
||||
|
||||
private String createTaggedNote(String tag) throws Exception {
|
||||
String createTaggedNote(String tag) throws Exception {
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "Tagged note creation with cURL");
|
||||
note.put("body", "An example of how to create a tagged note using cURL");
|
||||
@@ -162,77 +143,48 @@ public class GettingStartedDocumentation {
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").with(port(8080)).contentType(MediaTypes.HAL_JSON)
|
||||
.content(objectMapper.writeValueAsString(note)))
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().string("Location", notNullValue()))
|
||||
.andDo(documentCurlRequest(
|
||||
"notes/create_tagged_with_curl_request.asciidoc")
|
||||
.includeResponseHeaders())
|
||||
.andDo(documentCurlResponse(
|
||||
"notes/create_tagged_with_curl_response.asciidoc")
|
||||
.includeResponseHeaders()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
.andExpect(header().string("Location", notNullValue())).andReturn()
|
||||
.getResponse().getHeader("Location");
|
||||
return noteLocation;
|
||||
}
|
||||
|
||||
private void getTaggedNote(String tagLocation) throws Exception {
|
||||
this.mockMvc
|
||||
.perform(get(tagLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
void getTaggedNote(String tagLocation) throws Exception {
|
||||
this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(notNullValue())))
|
||||
.andExpect(jsonPath("body", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
||||
.andDo(documentCurlRequestAndResponse("notes/get_tagged_with_curl.asciidoc"));
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
}
|
||||
|
||||
private void getTags(String taggedNoteLocation) throws Exception {
|
||||
void getTags(String taggedNoteLocation) throws Exception {
|
||||
String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation))
|
||||
.andReturn(), "tags");
|
||||
this.mockMvc
|
||||
.perform(get(tagsLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1)))
|
||||
.andDo(documentCurlRequest("notes/get_tags_for_tagged_with_curl_request.asciidoc"))
|
||||
.andDo(documentCurlResponse("notes/get_tags_for_tagged_with_curl_response.asciidoc"));
|
||||
this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1)));
|
||||
}
|
||||
|
||||
private void tagExistingNote(String noteLocation, String tagLocation)
|
||||
throws Exception {
|
||||
void tagExistingNote(String noteLocation, String tagLocation) throws Exception {
|
||||
Map<String, Object> update = new HashMap<String, Object>();
|
||||
update.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
this.mockMvc
|
||||
.perform(
|
||||
patch(noteLocation).with(port(8080))
|
||||
.contentType(MediaTypes.HAL_JSON)
|
||||
.content(objectMapper.writeValueAsString(update)))
|
||||
.andExpect(status().isNoContent())
|
||||
.andDo(documentCurlRequest(
|
||||
"notes/tag_existing_with_curl_request.asciidoc")
|
||||
.includeResponseHeaders())
|
||||
.andDo(documentCurlResponse(
|
||||
"notes/tag_existing_with_curl_response.asciidoc")
|
||||
.includeResponseHeaders());
|
||||
this.mockMvc.perform(
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
objectMapper.writeValueAsString(update))).andExpect(
|
||||
status().isNoContent());
|
||||
|
||||
}
|
||||
|
||||
private void getTaggedExistingNote(String tagLocation) throws Exception {
|
||||
this.mockMvc
|
||||
.perform(get(tagLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(documentCurlRequestAndResponse("notes/get_tagged_existing_with_curl.asciidoc"));
|
||||
void getTaggedExistingNote(String tagLocation) throws Exception {
|
||||
this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private void getTagsForExistingNote(String taggedNoteLocation) throws Exception {
|
||||
void getTagsForExistingNote(String taggedNoteLocation) throws Exception {
|
||||
String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation))
|
||||
.andReturn(), "tags");
|
||||
this.mockMvc
|
||||
.perform(get(tagsLocation).with(port(8080)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1)))
|
||||
.andDo(print())
|
||||
.andDo(documentCurlRequestAndResponse("notes/get_tags_for_existing_with_curl.asciidoc"));
|
||||
this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_embedded.tags", hasSize(1)));
|
||||
}
|
||||
|
||||
private String getLink(MvcResult result, String href)
|
||||
|
||||
Reference in New Issue
Block a user