|
This version is still in development and is not considered stable yet. For the latest stable version, please use Spring REST Docs 4.0.0! |
Request and Response Payloads
In addition to the hypermedia-specific support described earlier, support for general documentation of request and response payloads is also provided.
By default, Spring REST Docs automatically generates snippets for the body of the request and the body of the response.
These snippets are named request-body.adoc and response-body.adoc respectively.
Request and Response Fields
To provide more detailed documentation of a request or response payload, support for documenting the payload’s fields is provided.
Consider the following payload:
{
"contact": {
"name": "Jane Doe",
"email": "[email protected]"
}
}
You can document the previous example’s fields as follows:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class ResponseFields {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", responseFields((1)
fieldWithPath("contact.email").description("The user's email address"), (2)
fieldWithPath("contact.name").description("The user's name")))); (3)
}
}
| 1 | Configure Spring REST docs to produce a snippet describing the fields in the response payload.
To document a request, you can use requestFields.
Both are static methods on org.springframework.restdocs.payload.PayloadDocumentation. |
| 2 | Expect a field with the path contact.email.
Uses the static fieldWithPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
| 3 | Expect a field with the path contact.name. |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class ResponseFields {
// Fields
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get()
.uri("user/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("user", responseFields((1)
fieldWithPath("contact.email").description("The user's email address"), (2)
fieldWithPath("contact.name").description("The user's name")))); (3)
}
}
| 1 | Configure Spring REST docs to produce a snippet describing the fields in the response payload.
To document a request, you can use requestFields.
Both are static methods on org.springframework.restdocs.payload.PayloadDocumentation. |
| 2 | Expect a field with the path contact.email.
Uses the static fieldWithPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
| 3 | Expect a field with the path contact.name. |
The result is a snippet that contains a table describing the fields.
For requests, this snippet is named request-fields.adoc.
For responses, this snippet is named response-fields.adoc.
When documenting fields, the test fails if an undocumented field is found in the payload. Similarly, the test also fails if a documented field is not found in the payload and the field has not been marked as optional.
If you do not want to provide detailed documentation for all of the fields, an entire subsection of a payload can be documented. The following examples show how to do so:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class Subsection {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", responseFields((1)
subsectionWithPath("contact").description("The user's contact details")))); (1)
}
}
| 1 | Document the subsection with the path contact.
contact.email and contact.name are now seen as having also been documented.
Uses the static subsectionWithPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class Subsection {
// Fields
private WebTestClient webTestClient;
@Test
void subsection() {
this.webTestClient.get()
.uri("user/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("user",
responseFields(subsectionWithPath("contact").description("The user's contact details")))); (1)
}
}
| 1 | Document the subsection with the path contact.
contact.email and contact.name are now seen as having also been documented.
Uses the static subsectionWithPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
subsectionWithPath can be useful for providing a high-level overview of a particular section of a payload.
You can then produce separate, more detailed documentation for a subsection.
See Documenting a Subsection of a Request or Response Payload.
If you do not want to document a field or subsection at all, you can mark it as ignored. This prevents it from appearing in the generated snippet while avoiding the failure described earlier.
You can also document fields in a relaxed mode, where any undocumented fields do not cause a test failure.
To do so, use the relaxedRequestFields and relaxedResponseFields methods on org.springframework.restdocs.payload.PayloadDocumentation.
This can be useful when documenting a particular scenario where you want to focus only on a subset of the payload.
By default, Spring REST Docs assumes that the payload you are documenting is JSON.
If you want to document an XML payload, the content type of the request or response must be compatible with application/xml.
|
Fields in JSON Payloads
This section describes support for documenting the fields of a JSON payload.
JSON Field Paths
JSON field paths use either dot notation or bracket notation.
Dot notation uses '.' to separate each key in the path (for example, a.b).
Bracket notation wraps each key in square brackets and single quotation marks (for example, ['a']['b']).
In either case, [] is used to identify an array.
Dot notation is more concise, but using bracket notation enables the use of . within a key name (for example, ['a.b']).
The two different notations can be used in the same path (for example, a['b']).
Consider the following JSON payload:
{
"a":{
"b":[
{
"c":"one"
},
{
"c":"two"
},
{
"d":"three"
}
],
"e.dot" : "four"
}
}
In the preceding JSON payload, the following paths are all present:
| Path | Value |
|---|---|
|
An object containing |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing the strings |
|
The string |
|
The string |
|
The string |
You can also document a payload that uses an array at its root.
The path [] refers to the entire array.
You can then use bracket or dot notation to identify fields within the array’s entries.
For example, [].id corresponds to the id field of every object found in the following array:
[
{
"id":1
},
{
"id":2
}
]
You can use * as a wildcard to match fields with different names.
For example, users.*.role could be used to document the role of every user in the following JSON:
{
"users":{
"ab12cd34":{
"role": "Administrator"
},
"12ab34cd":{
"role": "Guest"
}
}
}
JSON Field Types
When a field is documented, Spring REST Docs tries to determine its type by examining the payload. Seven different types are supported:
| Type | Description |
|---|---|
|
The value of each occurrence of the field is an array. |
|
The value of each occurrence of the field is a boolean ( |
|
The value of each occurrence of the field is an object. |
|
The value of each occurrence of the field is a number. |
|
The value of each occurrence of the field is |
|
The value of each occurrence of the field is a string. |
|
The field occurs multiple times in the payload with a variety of different types. |
You can also explicitly set the type by using the type(Object) method on FieldDescriptor.
The result of the toString method of the supplied Object is used in the documentation.
Typically, one of the values enumerated by JsonFieldType is used.
The following examples show how to do so:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class ExplicitFieldType {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
.description("The user's email address"))));
}
}
| 1 | Set the field’s type to String. |
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class ExplicitFieldType {
// Fields
private WebTestClient webTestClient;
void test() {
this.webTestClient.get()
.uri("user/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("user", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
.description("The user's email address"))));
}
}
| 1 | Set the field’s type to String. |
Reusing Field Descriptors
In addition to the general support for reusing snippets, the request and response snippets let additional descriptors be configured with a path prefix. This lets the descriptors for a repeated portion of a request or response payload be created once and then reused.
Consider an endpoint that returns a book:
{
"title": "Pride and Prejudice",
"author": "Jane Austen"
}
The paths for title and author are title and author, respectively.
Now consider an endpoint that returns an array of books:
[{
"title": "Pride and Prejudice",
"author": "Jane Austen"
},
{
"title": "To Kill a Mockingbird",
"author": "Harper Lee"
}]
The paths for title and author are [].title and [].author, respectively.
The only difference between the single book and the array of books is that the fields' paths now have a []. prefix.
You can create the descriptors that document a book as follows:
import org.springframework.restdocs.payload.FieldDescriptor;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
public final class BookPayload {
private BookPayload() {
}
public static FieldDescriptor[] bookDescription() {
return new FieldDescriptor[] { fieldWithPath("title").description("Title of the book"),
fieldWithPath("author").description("Author of the book") };
}
}
You can then use them to document a single book, as follows:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.docs.documentingyourapi.requestresponsepayloads.fields.reusingfielddescriptors.BookPayload;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class DescriptorReuse {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book", responseFields(BookPayload.bookDescription()))); (1)
}
}
| 1 | Document title and author by using existing descriptors |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.docs.documentingyourapi.requestresponsepayloads.fields.reusingfielddescriptors.BookPayload;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class DescriptorReuse {
// Fields
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get()
.uri("/books/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("book", responseFields(BookPayload.bookDescription()))); (1)
}
}
| 1 | Document title and author by using existing descriptors |
You can also use the descriptors to document an array of books, as follows:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.docs.documentingyourapi.requestresponsepayloads.fields.reusingfielddescriptors.BookPayload;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class ArrayDescriptorReuse {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book", responseFields(fieldWithPath("[]").description("An array of books")) (1)
.andWithPrefix("[].", BookPayload.bookDescription()))); (2)
}
}
| 1 | Document the array. |
| 2 | Document [].title and [].author by using the existing descriptors prefixed with []. |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.docs.documentingyourapi.requestresponsepayloads.fields.reusingfielddescriptors.BookPayload;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class ArrayDescriptorReuse {
// Fields
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get()
.uri("/books")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("books", responseFields(fieldWithPath("[]").description("An array of books")) (1)
.andWithPrefix("[].", BookPayload.bookDescription()))); (2)
}
}
| 1 | Document the array. |
| 2 | Document [].title and [].author by using the existing descriptors prefixed with []. |
Documenting a Subsection of a Request or Response Payload
If a payload is large or structurally complex, it can be useful to document individual sections of the payload. REST Docs let you do so by extracting a subsection of the payload and then documenting it.
Documenting a Subsection of a Request or Response Body
Consider the following JSON response body:
{
"weather": {
"wind": {
"speed": 15.3,
"direction": 287.0
},
"temperature": {
"high": 21.2,
"low": 14.8
}
}
}
You can produce a snippet that documents the temperature object as follows:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class BodySubsection {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("location", responseBody(beneathPath("weather.temperature")))); (1)
}
}
| 1 | Produce a snippet containing a subsection of the response body.
Uses the static responseBody and beneathPath methods on org.springframework.restdocs.payload.PayloadDocumentation.
To produce a snippet for the request body, you can use requestBody in place of responseBody. |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class BodySubsection {
// Fields
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get()
.uri("/locations/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("temperature", responseBody(beneathPath("weather.temperature")))); (1)
}
}
| 1 | Produce a snippet containing a subsection of the response body.
Uses the static responseBody and beneathPath methods on org.springframework.restdocs.payload.PayloadDocumentation.
To produce a snippet for the request body, you can use requestBody in place of responseBody. |
The result is a snippet with the following contents:
{
"temperature": {
"high": 21.2,
"low": 14.8
}
}
To make the snippet’s name distinct, an identifier for the subsection is included.
By default, this identifier is beneath-${path}.
For example, the preceding code results in a snippet named response-body-beneath-weather.temperature.adoc.
You can customize the identifier by using the withSubsectionId(String) method, as follows:
responseBody(beneathPath("weather.temperature").withSubsectionId("temp"));
The result is a snippet named request-body-temp.adoc.
Documenting the Fields of a Subsection of a Request or Response
As well as documenting a subsection of a request or response body, you can also document the fields in a particular subsection.
You can produce a snippet that documents the fields of the temperature object (high and low) as follows:
-
MockMvc
-
WebTestClient
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class FieldsSubsection {
// Fields
private MockMvc mockMvc;
@Test
void test() throws Exception {
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("location", responseFields(beneathPath("weather.temperature"), (1)
fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
fieldWithPath("low").description("The forecast low in degrees celcius"))));
}
}
| 1 | Produce a snippet describing the fields in the subsection of the response payload beneath the path weather.temperature.
Uses the static beneathPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
| 2 | Document the high and low fields. |
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
class FieldsSubsection {
// Fields
private WebTestClient webTestClient;
@Test
void test() {
this.webTestClient.get()
.uri("/locations/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("temperature", responseFields(beneathPath("weather.temperature"), (1)
fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
fieldWithPath("low").description("The forecast low in degrees celcius"))));
}
}
| 1 | Produce a snippet describing the fields in the subsection of the response payload beneath the path weather.temperature.
Uses the static beneathPath method on org.springframework.restdocs.payload.PayloadDocumentation. |
| 2 | Document the high and low fields. |
The result is a snippet that contains a table describing the high and low fields of weather.temperature.
To make the snippet’s name distinct, an identifier for the subsection is included.
By default, this identifier is beneath-${path}.
For example, the preceding code results in a snippet named response-fields-beneath-weather.temperature.adoc.