Restore support for REST Assured

Closes gh-784
This commit is contained in:
Andy Wilkinson
2022-01-21 15:33:21 +00:00
parent f5beaaacff
commit d38d79755b
53 changed files with 3403 additions and 11 deletions

View File

@@ -117,6 +117,7 @@ subprojects { subproject ->
samples {
dependOn "spring-restdocs-core:publishToMavenLocal"
dependOn "spring-restdocs-mockmvc:publishToMavenLocal"
dependOn "spring-restdocs-restassured:publishToMavenLocal"
dependOn "spring-restdocs-webtestclient:publishToMavenLocal"
dependOn "spring-restdocs-asciidoctor:publishToMavenLocal"
@@ -132,6 +133,10 @@ samples {
workingDir "$projectDir/samples/testng"
}
restAssured {
workingDir "$projectDir/samples/rest-assured"
}
webTestClient {
workingDir "$projectDir/samples/web-test-client"
}

View File

@@ -19,6 +19,7 @@ dependencies {
internal(enforcedPlatform("org.springframework:spring-framework-bom:$springFrameworkVersion"))
testImplementation(project(":spring-restdocs-mockmvc"))
testImplementation(project(":spring-restdocs-restassured"))
testImplementation(project(":spring-restdocs-webtestclient"))
testImplementation("jakarta.validation:jakarta.validation-api")
testImplementation("junit:junit")

View File

@@ -45,6 +45,17 @@ TIP: To configure a request's context path, use the `contextPath` method on `Moc
[[configuration-uris-rest-assured]]
==== REST Assured URI Customization
REST Assured tests a service by making actual HTTP requests. As a result, URIs must be
customized once the operation on the service has been performed but before it is
documented. A
<<customizing-requests-and-responses-preprocessors-modify-uris, REST-Assured-specific
preprocessor>> is provided for this purpose.
[[configuration-uris-webtestclient]]
==== WebTestClient URI Customization
@@ -79,6 +90,12 @@ include::{examples-dir}/com/example/mockmvc/CustomEncoding.java[tags=custom-enco
include::{examples-dir}/com/example/webtestclient/CustomEncoding.java[tags=custom-encoding]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomEncoding.java[tags=custom-encoding]
----
TIP: When Spring REST Docs converts the content of a request or a response to a `String`, the `charset` specified in the `Content-Type` header is used if it is available.
In its absence, the JVM's default `Charset` is used.
You can configure the JVM's default `Charset` by using the `file.encoding` system property.
@@ -105,6 +122,13 @@ include::{examples-dir}/com/example/mockmvc/CustomFormat.java[tags=custom-format
include::{examples-dir}/com/example/webtestclient/CustomFormat.java[tags=custom-format]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomFormat.java[tags=custom-format]
----
====
[[configuration-default-snippets]]
@@ -134,6 +158,12 @@ include::{examples-dir}/com/example/mockmvc/CustomDefaultSnippets.java[tags=cust
include::{examples-dir}/com/example/webtestclient/CustomDefaultSnippets.java[tags=custom-default-snippets]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomDefaultSnippets.java[tags=custom-default-snippets]
----
[[configuration-default-preprocessors]]
@@ -158,4 +188,10 @@ include::{examples-dir}/com/example/webtestclient/CustomDefaultOperationPreproce
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomDefaultOperationPreprocessors.java[tags=custom-default-operation-preprocessors]
----
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.

View File

@@ -24,6 +24,14 @@ include::{examples-dir}/com/example/webtestclient/PerTestPreprocessing.java[tags
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.
.REST Assured
----
include::{examples-dir}/com/example/restassured/PerTestPreprocessing.java[tags=preprocessing]
----
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.
====
Alternatively, you may want to apply the same preprocessors to every test.
You can do so by using the `RestDocumentationConfigurer` API in your `@Before` method to configure the preprocessors.
For example, to remove the `Foo` header from all requests and pretty print all responses, you could do one of the following (depending on your testing environment):
@@ -44,6 +52,14 @@ include::{examples-dir}/com/example/webtestclient/EveryTestPreprocessing.java[ta
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=setup]
----
<1> Apply a request preprocessor that removes the header named `Foo`.
<2> Apply a response preprocessor that pretty prints its content.
Then, in each test, you can perform any configuration specific to that test.
The following examples show how to do so:
@@ -59,6 +75,12 @@ include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=use
include::{examples-dir}/com/example/webtestclient/EveryTestPreprocessing.java[tags=use]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=use]
----
Various built-in preprocessors, including those illustrated above, are available through the static methods on `Preprocessors`.
See <<Preprocessors, below>> for further details.
@@ -116,7 +138,7 @@ You can use `modifyParameters` on `Preprocessors` to add, set, and remove reques
TIP: If you use MockMvc or a WebTestClient that is not bound to a server, you should customize URIs by <<configuration-uris, changing the configuration>>.
You can use `modifyUris` on `Preprocessors` to modify any URIs in a request or a response.
When using WebTestClient bound to a server, this lets you customize the URIs that appear in the documentation while testing a local instance of the service.
When using REST Assured or WebTestClient bound to a server, this lets you customize the URIs that appear in the documentation while testing a local instance of the service.

View File

@@ -33,6 +33,18 @@ Uses the static `links` method on `org.springframework.restdocs.hypermedia.Hyper
Uses the static `linkWithRel` method on `org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
<3> Expect a link whose `rel` is `bravo`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Hypermedia.java[tag=links]
----
<1> Configure Spring REST docs to produce a snippet describing the response's links.
Uses the static `links` method on
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
<2> Expect a link whose `rel` is `alpha`. Uses the static `linkWithRel` method on
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
<3> Expect a link whose `rel` is `bravo`.
The result is a snippet named `links.adoc` that contains a table describing the resource's links.
TIP: If a link in the response has a `title`, you can omit the description from its descriptor and the `title` is used.
@@ -79,6 +91,14 @@ include::{examples-dir}/com/example/webtestclient/Hypermedia.java[tag=explicit-e
<1> Indicate that the links are in HAL format.
Uses the static `halLinks` method on `org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Hypermedia.java[tag=explicit-extractor]
----
<1> Indicate that the links are in HAL format. Uses the static `halLinks` method on
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
If your API represents its links in a format other than Atom or HAL, you can provide your own implementation of the `LinkExtractor` interface to extract the links from the response.
@@ -150,6 +170,18 @@ Both are static methods on `org.springframework.restdocs.payload.PayloadDocument
Uses the static `fieldWithPath` method on `org.springframework.restdocs.payload.PayloadDocumentation`.
<3> Expect a field with the path `contact.name`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=response]
----
<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`.
@@ -178,6 +210,14 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=subsection]
`contact.email` and `contact.name` are now seen as having also been documented.
Uses the static `subsectionWithPath` method on `org.springframework.restdocs.payload.PayloadDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=subsection]
----
<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-your-api-request-response-payloads-subsections>>.
@@ -357,6 +397,13 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=explicit-typ
----
<1> Set the field's type to `String`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=explicit-type]
----
<1> Set the field's type to `String`.
[[documenting-your-api-request-response-payloads-fields-xml]]
@@ -440,6 +487,13 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=single-book]
----
<1> Document `title` and `author` by using existing descriptors
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=single-book]
----
<1> Document `title` and `author` by using existing descriptors
You can also use the descriptors to document an array of books, as follows:
[source,java,indent=0,role="primary"]
@@ -460,6 +514,16 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=book-array]
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=book-array]
----
<1> Document the array.
<2> Document `[].title` and `[].author` by using the existing descriptors prefixed with `[].`
[[documenting-your-api-request-response-payloads-subsections]]
==== Documenting a Subsection of a Request or Response Payload
@@ -509,6 +573,15 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=body-subsect
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`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=body-subsection]
----
<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:
[source,json,indent=0]
@@ -559,6 +632,16 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=fields-subse
Uses the static `beneathPath` method on `org.springframework.restdocs.payload.PayloadDocumentation`.
<2> Document the `high` and `low` fields.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=fields-subsection]
----
<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}`.
@@ -597,6 +680,18 @@ Uses the static `requestParameters` method on `org.springframework.restdocs.requ
Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
<4> Document the `per_page` parameter.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestParameters.java[tags=request-parameters-query-string]
----
<1> Configure Spring REST Docs to produce a snippet describing the request's parameters.
Uses the static `requestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`.
<2> Document the `page` parameter.
Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
<3> Document the `per_page` parameter.
<4> Perform a `GET` request with two parameters, `page` and `per_page`, in the query string.
You can also include request parameters as form data in the body of a POST request.
The following examples show how to do so:
@@ -614,6 +709,14 @@ include::{examples-dir}/com/example/webtestclient/RequestParameters.java[tags=re
----
<1> Perform a `POST` request with a single parameter, `username`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestParameters.java[tags=request-parameters-form-data]
----
<1> Configure the `username` parameter.
<2> Perform the `POST` request.
In all cases, the result is a snippet named `request-parameters.adoc` that contains a table describing the parameters that are supported by the resource.
When documenting request parameters, the test fails if an undocumented request parameter is used in the request.
@@ -658,6 +761,18 @@ Uses the static `pathParameters` method on `org.springframework.restdocs.request
Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
<4> Document the parameter named `longitude`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/PathParameters.java[tags=path-parameters]
----
<1> Configure Spring REST Docs to produce a snippet describing the request's path parameters.
Uses the static `pathParameters` method on `org.springframework.restdocs.request.RequestDocumentation`.
<2> Document the parameter named `latitude`.
Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
<3> Document the parameter named `longitude`.
<4> Perform a `GET` request with two path parameters, `latitude` and `longitude`.
The result is a snippet named `path-parameters.adoc` that contains a table describing the path parameters that are supported by the resource.
TIP: If you use MockMvc, to make the path parameters available for documentation, you must build the request by using one of the methods on `RestDocumentationRequestBuilders` rather than `MockMvcRequestBuilders`.
@@ -702,6 +817,17 @@ Uses the static `requestParts` method on `org.springframework.restdocs.request.R
<3> Document the part named `file`.
Uses the static `partWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestParts.java[tags=request-parts]
----
<1> Configure Spring REST Docs to produce a snippet describing the request's parts.
Uses the static `requestParts` method on `org.springframework.restdocs.request.RequestDocumentation`.
<2> Document the part named `file`. Uses the static `partWithName` method on `org.springframework.restdocs.request.RequestDocumentation`.
<3> Configure the request with the part named `file`.
<4> Perform the `POST` request to `/upload`.
The result is a snippet named `request-parts.adoc` that contains a table describing the request parts that are supported by the resource.
When documenting request parts, the test fails if an undocumented part is used in the request.
@@ -744,6 +870,14 @@ include::{examples-dir}/com/example/webtestclient/RequestPartPayload.java[tags=b
<1> Configure Spring REST docs to produce a snippet containing the body of the request part named `metadata`.
Uses the static `requestPartBody` method on `PayloadDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=body]
----
<1> Configure Spring REST docs to produce a snippet containing the body of the request part named `metadata`.
Uses the static `requestPartBody` method on `PayloadDocumentation`.
The result is a snippet named `request-part-${part-name}-body.adoc` that contains the part's body.
For example, documenting a part named `metadata` produces a snippet named `request-part-metadata-body.adoc`.
@@ -774,6 +908,16 @@ Uses the static `requestPartFields` method on `PayloadDocumentation`.
<2> Expect a field with the path `version`.
Uses the static `fieldWithPath` method on `org.springframework.restdocs.payload.PayloadDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=fields]
----
<1> Configure Spring REST docs to produce a snippet describing the fields in the payload of the request part named `metadata`.
Uses the static `requestPartFields` method on `PayloadDocumentation`.
<2> Expect a field with the path `version`.
Uses the static `fieldWithPath` method on `org.springframework.restdocs.payload.PayloadDocumentation`.
The result is a snippet that contains a table describing the part's fields.
This snippet is named `request-part-${part-name}-fields.adoc`.
For example, documenting a part named `metadata` produces a snippet named `request-part-metadata-fields.adoc`.
@@ -825,6 +969,19 @@ Uses the static `headerWithName` method on `org.springframework.restdocs.headers
<4> Produce a snippet describing the response's headers.
Uses the static `responseHeaders` method on `org.springframework.restdocs.headers.HeaderDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/HttpHeaders.java[tags=headers]
----
<1> Configure Spring REST Docs to produce a snippet describing the request's headers.
Uses the static `requestHeaders` method on `org.springframework.restdocs.headers.HeaderDocumentation`.
<2> Document the `Authorization` header.
Uses the static `headerWithName` method on `org.springframework.restdocs.headers.HeaderDocumentation.
<3> Produce a snippet describing the response's headers.
Uses the static `responseHeaders` method on `org.springframework.restdocs.headers.HeaderDocumentation`.
<4> Configure the request with an `Authorization` header that uses basic authentication.
The result is a snippet named `request-headers.adoc` and a snippet named `response-headers.adoc`.
Each contains a table describing the headers.
@@ -863,6 +1020,13 @@ include::{examples-dir}/com/example/webtestclient/WebTestClientSnippetReuse.java
----
<1> Reuse the `pagingLinks` `Snippet`, calling `and` to add descriptors that are specific to the resource that is being documented.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RestAssuredSnippetReuse.java[tags=use]
----
<1> Reuse the `pagingLinks` `Snippet`, calling `and` to add descriptors that are specific to the resource that is being documented.
The result of the example is that links with `rel` values of `first`, `last`, `next`, `previous`, `alpha`, and `bravo` are all documented.
@@ -1045,6 +1209,12 @@ The following examples show how to do so:
include::{examples-dir}/com/example/mockmvc/ParameterizedOutput.java[tags=parameterized-output]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/ParameterizedOutput.java[tags=parameterized-output]
----
[source,java,indent=0,role="secondary"]
.WebTestClient
----
@@ -1112,8 +1282,16 @@ include::{examples-dir}/com/example/webtestclient/Payload.java[tags=constraints]
<2> Set the `constraints` attribute for the `name` field.
<3> Set the `constraints` attribute for the `email` field.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=constraints]
----
<1> Configure the `title` attribute for the request fields snippet.
<2> Set the `constraints` attribute for the `name` field.
<3> Set the `constraints` attribute for the `email` field.
The second step is to provide a custom template named `request-fields.snippet` that includes the information about the fields' constraints in the generated snippet's table and adds a title.
The following example shows how to do so:
[source,indent=0]
----

View File

@@ -34,6 +34,19 @@ If you want to jump straight in, a number of sample applications are available:
| Demonstrates the use of Spring REST docs with Spring WebFlux's WebTestClient.
|===
[cols="3,2,10"]
.REST Assured
|===
| Sample | Build system | Description
| {samples}/rest-assured[REST Assured]
| Gradle
| Demonstrates the use of Spring REST Docs with http://rest-assured.io[REST Assured].
|===
[cols="3,2,10"]
.Advanced
|===
@@ -63,7 +76,7 @@ Spring REST Docs has the following minimum requirements:
* Java 17
* Spring Framework 6
Additionally, the `spring-restdocs-restassured` module requires REST Assured 4 (4.5 or later).
[[getting-started-build-configuration]]
=== Build configuration
@@ -113,7 +126,7 @@ The key parts of the configuration are described in the following listings:
</build>
----
<1> Add a dependency on `spring-restdocs-mockmvc` in the `test` scope.
If you want to use `WebTestClient` add a dependency on `spring-restdocs-webtestclient` instead.
If you want to use `WebTestClient` or REST Assured rather than MockMvc, add a dependency on `spring-restdocs-webtestclient` or `spring-restdocs-restassured` respectively instead.
<2> Add the Asciidoctor plugin.
<3> Using `prepare-package` allows the documentation to be <<getting-started-build-configuration-packaging-the-documentation, included in the package>>.
<4> Add `spring-restdocs-asciidoctor` as a dependency of the Asciidoctor plugin.
@@ -156,7 +169,7 @@ It will also allow you to use the `operation` block macro.
This will automatically configure the `snippets` attribute for use in your `.adoc` files to point to `build/generated-snippets`.
It will also allow you to use the `operation` block macro.
<4> Add a dependency on `spring-restdocs-mockmvc` in the `testImplementation` configuration.
If you want to use `WebTestClient`, add a dependency on `spring-restdocs-webtestclient` instead.
If you want to use `WebTestClient` or REST Assured rather than MockMvc, add a dependency on `spring-restdocs-webtestclient` or `spring-restdocs-restassured` respectively instead.
<5> Configure a property to define the output location for generated snippets.
<6> Configure the `test` task to add the snippets directory as an output.
<7> Configure the `asciidoctor` task.
@@ -232,7 +245,7 @@ The following listings show how to do so in both Maven and Gradle:
[[getting-started-documentation-snippets]]
=== Generating Documentation Snippets
Spring REST Docs uses Spring MVC's {spring-framework-docs}/testing.html#spring-mvc-test-framework[test framework] or Spring WebFlux's {spring-framework-docs}/testing.html#webtestclient[`WebTestClient`] to make requests to the service that you are documenting.
Spring REST Docs uses Spring MVC's {spring-framework-docs}/testing.html#spring-mvc-test-framework[test framework] or Spring WebFlux's {spring-framework-docs}/testing.html#webtestclient[`WebTestClient`], or https://rest-assured.io[REST Assured] to make requests to the service that you are documenting.
It then produces documentation snippets for the request and the resulting response.
@@ -280,7 +293,7 @@ The following example shows how to do so:
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("custom");
----
Next, you must provide an `@Before` method to configure MockMvc or WebTestClient.
Next, you must provide an `@Before` method to configure MockMvc or WebTestClient, or REST Assured.
The following examples show how to do so:
[source,java,indent=0,role="primary"]
@@ -299,6 +312,14 @@ include::{examples-dir}/com/example/webtestclient/ExampleApplicationTests.java[t
<1> The `WebTestClient` instance is configured by adding a `WebTestclientRestDocumentationConfigurer` as an `ExchangeFilterFunction`.
You can obtain an instance of this class from the static `documentationConfiguration()` method on `org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/ExampleApplicationTests.java[tags=setup]
----
<1> REST Assured is configured by adding a `RestAssuredRestDocumentationConfigurer` as a `Filter`.
You can obtain an instance of this class from the static `documentationConfiguration()` method on `RestAssuredRestDocumentation` in the `org.springframework.restdocs.restassured` package.
The configurer applies sensible defaults and also provides an API for customizing the configuration.
See the <<configuration, configuration section>> for more information.
@@ -350,7 +371,7 @@ public class JUnit5ExampleTests {
}
----
Next, you must provide a `@BeforeEach` method to configure MockMvc or WebTestClient.
Next, you must provide a `@BeforeEach` method to configure MockMvc or WebTestClient, or REST Assured.
The following listings show how to do so:
[source,java,indent=0,role="primary"]
@@ -369,6 +390,14 @@ include::{examples-dir}/com/example/webtestclient/ExampleApplicationJUnit5Tests.
<1> The `WebTestClient` instance is configured by adding a `WebTestClientRestDocumentationConfigurer` as an `ExchangeFilterFunction`.
You can obtain an instance of this class from the static `documentationConfiguration()` method on `org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/ExampleApplicationJUnit5Tests.java[tags=setup]
----
<1> REST Assured is configured by adding a `RestAssuredRestDocumentationConfigurer` as a `Filter`.
You can obtain an instance of this class from the static `documentationConfiguration()` method on `RestAssuredRestDocumentation` in the `org.springframework.restdocs.restassured` package.
The configurer applies sensible defaults and also provides an API for customizing the configuration.
See the <<configuration, configuration section>> for more information.
@@ -391,7 +420,7 @@ private ManualRestDocumentation restDocumentation = new ManualRestDocumentation(
----
Secondly, you must call `ManualRestDocumentation.beforeTest(Class, String)` before each test.
You can do so as part of the method that configures MockMvc or WebTestClient.
You can do so as part of the method that configures MockMvc, WebTestClient, or REST Assured.
The following examples show how to do so:
[source,java,indent=0,role="primary"]
@@ -406,6 +435,12 @@ include::{examples-dir}/com/example/mockmvc/ExampleApplicationTestNgTests.java[t
include::{examples-dir}/com/example/webtestclient/ExampleApplicationTestNgTests.java[tags=setup]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/ExampleApplicationTestNgTests.java[tags=setup]
----
Finally, you must call `ManualRestDocumentation.afterTest` after each test.
The following example shows how to do so with TestNG:
@@ -444,6 +479,19 @@ include::{examples-dir}/com/example/webtestclient/InvokeService.java[tags=invoke
The snippets are written by a `Consumer` of the `ExchangeResult`.
You can obtain such a consumer from the static `document` method on `org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/InvokeService.java[tags=invoke-service]
----
<1> Apply the specification that was initialized in the `@Before` method.
<2> Indicate that an `application/json` response is required.
<3> Document the call to the service, writing the snippets into a directory named `index` (which is located beneath the configured output directory).
The snippets are written by a `RestDocumentationFilter`.
You can obtain an instance of this class from the static `document` method on `RestAssuredRestDocumentation` in the `org.springframework.restdocs.restassured` package.
<4> Invoke the root (`/`) of the service.
<5> Assert that the service produce the expected response.
By default, six snippets are written:
* `<output-directory>/index/curl-request.adoc`

View File

@@ -9,7 +9,7 @@ To this end, Spring REST Docs uses https://asciidoctor.org[Asciidoctor] by defau
Asciidoctor processes plain text and produces HTML, styled and laid out to suit your needs.
If you prefer, you can also configure Spring REST Docs to use Markdown.
Spring REST Docs uses snippets produced by tests written with Spring MVC's {spring-framework-docs}/testing.html#spring-mvc-test-framework[test framework] or Spring WebFlux's {spring-framework-docs}/testing.html#webtestclient[`WebTestClient`].
Spring REST Docs uses snippets produced by tests written with Spring MVC's {spring-framework-docs}/testing.html#spring-mvc-test-framework[test framework], Spring WebFlux's {spring-framework-docs}/testing.html#webtestclient[`WebTestClient`] or http://rest-assured.io[REST Assured 4].
This test-driven approach helps to guarantee the accuracy of your service's documentation.
If a snippet is incorrect, the test that produces it fails.

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultOperationPreprocessors {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
@Before
public void setup() {
// tag::custom-default-operation-preprocessors[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
// end::custom-default-operation-preprocessors[]
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.cli.CliDocumentation.curlRequest;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
@Before
public void setUp() {
// tag::custom-default-snippets[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).snippets().withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomEncoding {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
@Before
public void setUp() {
// tag::custom-encoding[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).snippets().withEncoding("ISO-8859-1"))
.build();
// end::custom-encoding[]
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.templates.TemplateFormats;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomFormat {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
@Before
public void setUp() {
// tag::custom-format[]
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation).snippets()
.withTemplateFormat(TemplateFormats.markdown())).build();
// end::custom-format[]
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class EveryTestPreprocessing {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
// tag::setup[]
private RequestSpecification spec;
@Before
public void setup() {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) // <1>
.withResponseDefaults(prettyPrint())) // <2>
.build();
}
// end::setup[]
public void use() throws Exception {
// tag::use[]
RestAssured.given(this.spec)
.filter(document("index", links(linkWithRel("self").description("Canonical self link")))).when()
.get("/").then().assertThat().statusCode(is(200));
// end::use[]
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
@ExtendWith(RestDocumentationExtension.class)
public class ExampleApplicationJUnit5Tests {
@SuppressWarnings("unused")
// tag::setup[]
private RequestSpecification spec;
@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)) // <1>
.build();
}
// end::setup[]
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import java.lang.reflect.Method;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.springframework.restdocs.ManualRestDocumentation;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class ExampleApplicationTestNgTests {
private final ManualRestDocumentation restDocumentation = new ManualRestDocumentation();
@SuppressWarnings("unused")
// tag::setup[]
private RequestSpecification spec;
@BeforeMethod
public void setUp(Method method) {
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)).build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}
// end::setup[]
// tag::teardown[]
@AfterMethod
public void tearDown() {
this.restDocumentation.afterTest();
}
// end::teardown[]
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class ExampleApplicationTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
// tag::setup[]
private RequestSpecification spec;
@Before
public void setUp() {
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)) // <1>
.build();
}
// end::setup[]
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class HttpHeaders {
private RequestSpecification spec;
public void headers() throws Exception {
// tag::headers[]
RestAssured.given(this.spec).filter(document("headers", requestHeaders(// <1>
headerWithName("Authorization").description("Basic auth credentials")), // <2>
responseHeaders(// <3>
headerWithName("X-RateLimit-Limit")
.description("The total number of requests permitted per period"),
headerWithName("X-RateLimit-Remaining")
.description("Remaining requests permitted in current period"),
headerWithName("X-RateLimit-Reset")
.description("Time at which the rate limit period will reset"))))
.header("Authorization", "Basic dXNlcjpzZWNyZXQ=") // <4>
.when().get("/people").then().assertThat().statusCode(is(200));
// end::headers[]
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.halLinks;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class Hypermedia {
private RequestSpecification spec;
public void defaultExtractor() throws Exception {
// tag::links[]
RestAssured.given(this.spec).accept("application/json").filter(document("index", links(// <1>
linkWithRel("alpha").description("Link to the alpha resource"), // <2>
linkWithRel("bravo").description("Link to the bravo resource")))) // <3>
.get("/").then().assertThat().statusCode(is(200));
// end::links[]
}
public void explicitExtractor() throws Exception {
RestAssured.given(this.spec).accept("application/json")
// tag::explicit-extractor[]
.filter(document("index", links(halLinks(), // <1>
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))))
// end::explicit-extractor[]
.get("/").then().assertThat().statusCode(is(200));
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class InvokeService {
private RequestSpecification spec;
public void invokeService() throws Exception {
// tag::invoke-service[]
RestAssured.given(this.spec) // <1>
.accept("application/json") // <2>
.filter(document("index")) // <3>
.when().get("/") // <4>
.then().assertThat().statusCode(is(200)); // <5>
// end::invoke-service[]
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class ParameterizedOutput {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@SuppressWarnings("unused")
private RequestSpecification spec;
// tag::parameterized-output[]
@Before
public void setUp() {
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(document("{method-name}/{step}")).build();
}
// end::parameterized-output[]
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class PathParameters {
private RequestSpecification spec;
public void pathParametersSnippet() throws Exception {
// tag::path-parameters[]
RestAssured.given(this.spec).filter(document("locations", pathParameters(// <1>
parameterWithName("latitude").description("The location's latitude"), // <2>
parameterWithName("longitude").description("The location's longitude")))) // <3>
.when().get("/locations/{latitude}/{longitude}", 51.5072, 0.1275) // <4>
.then().assertThat().statusCode(is(200));
// end::path-parameters[]
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
public class Payload {
private RequestSpecification spec;
public void response() throws Exception {
// tag::response[]
RestAssured.given(this.spec).accept("application/json").filter(document("user", responseFields(// <1>
fieldWithPath("contact.name").description("The user's name"), // <2>
fieldWithPath("contact.email").description("The user's email address")))) // <3>
.when().get("/user/5").then().assertThat().statusCode(is(200));
// end::response[]
}
public void subsection() throws Exception {
// tag::subsection[]
RestAssured.given(this.spec).accept("application/json")
.filter(document("user",
responseFields(subsectionWithPath("contact").description("The user's contact details")))) // <1>
.when().get("/user/5").then().assertThat().statusCode(is(200));
// end::subsection[]
}
public void explicitType() throws Exception {
RestAssured.given(this.spec).accept("application/json")
// tag::explicit-type[]
.filter(document("user", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) // <1>
.description("The user's email address"))))
// end::explicit-type[]
.when().get("/user/5").then().assertThat().statusCode(is(200));
}
public void constraints() throws Exception {
RestAssured.given(this.spec).accept("application/json")
// tag::constraints[]
.filter(document("create-user",
requestFields(attributes(key("title").value("Fields for user creation")), // <1>
fieldWithPath("name").description("The user's name")
.attributes(key("constraints").value("Must not be null. Must not be empty")), // <2>
fieldWithPath("email").description("The user's email address")
.attributes(key("constraints").value("Must be a valid email address"))))) // <3>
// end::constraints[]
.when().post("/users").then().assertThat().statusCode(is(200));
}
public void descriptorReuse() throws Exception {
FieldDescriptor[] book = new FieldDescriptor[] { fieldWithPath("title").description("Title of the book"),
fieldWithPath("author").description("Author of the book") };
// tag::single-book[]
RestAssured.given(this.spec).accept("application/json").filter(document("book", responseFields(book))) // <1>
.when().get("/books/1").then().assertThat().statusCode(is(200));
// end::single-book[]
// tag::book-array[]
RestAssured.given(this.spec).accept("application/json")
.filter(document("books", responseFields(fieldWithPath("[]").description("An array of books")) // <1>
.andWithPrefix("[].", book))) // <2>
.when().get("/books").then().assertThat().statusCode(is(200));
// end::book-array[]
}
public void fieldsSubsection() throws Exception {
// tag::fields-subsection[]
RestAssured.given(this.spec).accept("application/json")
.filter(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"))))
.when().get("/locations/1").then().assertThat().statusCode(is(200));
// end::fields-subsection[]
}
public void bodySubsection() throws Exception {
// tag::body-subsection[]
RestAssured.given(this.spec).accept("application/json")
.filter(document("location", responseBody(beneathPath("weather.temperature")))) // <1>
.when().get("/locations/1").then().assertThat().statusCode(is(200));
// end::body-subsection[]
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class PerTestPreprocessing {
private RequestSpecification spec;
public void general() throws Exception {
// tag::preprocessing[]
RestAssured.given(this.spec).filter(document("index", preprocessRequest(removeHeaders("Foo")), // <1>
preprocessResponse(prettyPrint()))) // <2>
.when().get("/").then().assertThat().statusCode(is(200));
// end::preprocessing[]
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class RequestParameters {
private RequestSpecification spec;
public void getQueryStringSnippet() throws Exception {
// tag::request-parameters-query-string[]
RestAssured.given(this.spec).filter(document("users", requestParameters(// <1>
parameterWithName("page").description("The page to retrieve"), // <2>
parameterWithName("per_page").description("Entries per page")))) // <3>
.when().get("/users?page=2&per_page=100") // <4>
.then().assertThat().statusCode(is(200));
// end::request-parameters-query-string[]
}
public void postFormDataSnippet() throws Exception {
// tag::request-parameters-form-data[]
RestAssured.given(this.spec)
.filter(document("create-user",
requestParameters(parameterWithName("username").description("The user's username"))))
.formParam("username", "Tester") // <1>
.when().post("/users") // <2>
.then().assertThat().statusCode(is(200));
// end::request-parameters-form-data[]
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartFields;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class RequestPartPayload {
private RequestSpecification spec;
public void fields() throws Exception {
// tag::fields[]
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
.filter(document("image-upload", requestPartFields("metadata", // <1>
fieldWithPath("version").description("The version of the image")))) // <2>
.when().multiPart("image", new File("image.png"), "image/png").multiPart("metadata", metadata)
.post("images").then().assertThat().statusCode(is(200));
// end::fields[]
}
public void body() throws Exception {
// tag::body[]
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
.filter(document("image-upload", requestPartBody("metadata"))) // <1>
.when().multiPart("image", new File("image.png"), "image/png").multiPart("metadata", metadata)
.post("images").then().assertThat().statusCode(is(200));
// end::body[]
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class RequestParts {
private RequestSpecification spec;
public void upload() throws Exception {
// tag::request-parts[]
RestAssured.given(this.spec).filter(document("users", requestParts(// <1>
partWithName("file").description("The file to upload")))) // <2>
.multiPart("file", "example") // <3>
.when().post("/upload") // <4>
.then().statusCode(is(200));
// end::request-parts[]
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2014-2021 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
*
* https://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 com.example.restassured;
import com.example.SnippetReuse;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
public class RestAssuredSnippetReuse extends SnippetReuse {
private RequestSpecification spec;
public void documentation() throws Exception {
// tag::use[]
RestAssured.given(this.spec).accept("application/json").filter(document("example", this.pagingLinks.and(// <1>
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource")))).get("/").then().assertThat()
.statusCode(is(200));
// end::use[]
}
}

View File

@@ -0,0 +1,63 @@
plugins {
id "java"
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
repositories {
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
mavenCentral()
mavenLocal()
}
group = 'com.example'
sourceCompatibility = 17
targetCompatibility = 17
ext {
restdocsVersion = '3.0.0-SNAPSHOT'
snippetsDir = file('build/generated-snippets')
}
configurations {
asciidoctorExtensions
}
dependencies {
asciidoctorExtensions "org.springframework.restdocs:spring-restdocs-asciidoctor:$restdocsVersion"
implementation platform("org.springframework:spring-framework-bom:6.0.0-M2")
implementation 'io.projectreactor.netty:reactor-netty-http:1.0.15'
implementation 'org.springframework:spring-context'
implementation 'org.springframework:spring-webflux'
testImplementation 'io.rest-assured:rest-assured:4.5.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0'
testImplementation "org.springframework.restdocs:spring-restdocs-restassured:$restdocsVersion"
testImplementation 'org.springframework:spring-test'
testImplementation('org.junit.vintage:junit-vintage-engine') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0'
}
test {
outputs.dir snippetsDir
useJUnitPlatform()
}
asciidoctor {
configurations "asciidoctorExtensions"
inputs.dir snippetsDir
dependsOn test
}
jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
samples/rest-assured/gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
samples/rest-assured/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

View File

@@ -0,0 +1,26 @@
= Spring REST Docs REST Assured Sample
Andy Wilkinson;
:doctype: book
:icons: font
:source-highlighter: highlightjs
Sample application demonstrating how to use Spring REST Docs with REST Assured.
`SampleRestAssuredApplicationTests` makes a call to a very simple service. The service
that is being tested is running on a random port on `localhost`. The tests make use of a
preprocessor to modify the request so that it appears to have been sent to
`https://api.example.com`. If your service includes URIs in its responses, for example
because it uses hypermedia, similar preprocessing can be applied to the response before
it is documented.
Three snippets are produced. One showing how to make a request using cURL:
include::{snippets}/sample/curl-request.adoc[]
One showing the HTTP request:
include::{snippets}/sample/http-request.adoc[]
And one showing the HTTP response:
include::{snippets}/sample/http-response.adoc[]

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2014-2022 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
*
* https://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 com.example.restassured;
import java.net.InetSocketAddress;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
@EnableWebFlux
@Configuration
class SampleRestAssuredApplication {
@Bean
RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions.route(RequestPredicates.GET("/"),
(request) -> ServerResponse.status(HttpStatus.OK).bodyValue("Hello, World"));
}
@Bean
WebServer webServer(RouterFunction<ServerResponse> routerFunction) {
return new WebServer(routerFunction);
}
static class WebServer implements DisposableBean {
private final DisposableServer httpServer;
WebServer(RouterFunction<?> routerFunction) {
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(RouterFunctions.toHttpHandler(routerFunction));
HttpServer httpServer = HttpServer.create().handle(adapter);
this.httpServer = httpServer.bindNow();
}
@Override
public void destroy() throws Exception {
this.httpServer.disposeNow();
}
int getPort() {
return ((InetSocketAddress)this.httpServer.address()).getPort();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2014-2022 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
*
* https://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 com.example.restassured;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.example.restassured.SampleRestAssuredApplication.WebServer;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
@ContextConfiguration(classes = SampleRestAssuredApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SampleRestAssuredApplicationTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private RequestSpecification documentationSpec;
@Autowired
private WebServer webServer;
private int port;
@Before
public void setUp() {
this.documentationSpec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation)).build();
this.port = webServer.getPort();
}
@Test
public void sample() throws Exception {
given(this.documentationSpec)
.accept("text/plain")
.filter(document("sample",
preprocessRequest(modifyUris()
.scheme("https")
.host("api.example.com")
.removePort())))
.when()
.port(this.port)
.get("/")
.then()
.assertThat().statusCode(is(200));
}
}

View File

@@ -39,4 +39,5 @@ include "spring-restdocs-asciidoctor"
include "spring-restdocs-core"
include "spring-restdocs-mockmvc"
include "spring-restdocs-platform"
include "spring-restdocs-restassured"
include "spring-restdocs-webtestclient"

View File

@@ -12,6 +12,7 @@ dependencies {
api("jakarta.servlet:jakarta.servlet-api:5.0.0")
api("jakarta.validation:jakarta.validation-api:3.0.0")
api("junit:junit:4.12")
api("io.rest-assured:rest-assured:4.5.0")
api("org.apache.pdfbox:pdfbox:2.0.7")
api("org.asciidoctor:asciidoctorj:2.5.2")
api("org.asciidoctor:asciidoctorj-pdf:1.6.0")

View File

@@ -0,0 +1,24 @@
plugins {
id "java-library"
id "maven-publish"
}
description = "Spring REST Docs REST Assured"
dependencies {
api(project(":spring-restdocs-core"))
api("io.rest-assured:rest-assured") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation("org.springframework:spring-web")
internal(platform(project(":spring-restdocs-platform")))
testImplementation(testFixtures(project(":spring-restdocs-core")))
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("junit:junit")
testImplementation("org.apache.tomcat.embed:tomcat-embed-core:10.0.11")
testImplementation("org.assertj:assertj-core")
testImplementation("org.hamcrest:hamcrest-library")
testImplementation("org.mockito:mockito-core")
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.springframework.restdocs.config.OperationPreprocessorsConfigurer;
/**
* A configurer that can be used to configure the operation preprocessors when using REST
* Assured.
*
* @author Filip Hrisafov
* @since 2.0.0
*/
public final class RestAssuredOperationPreprocessorsConfigurer extends
OperationPreprocessorsConfigurer<RestAssuredRestDocumentationConfigurer, RestAssuredOperationPreprocessorsConfigurer>
implements Filter {
RestAssuredOperationPreprocessorsConfigurer(RestAssuredRestDocumentationConfigurer parent) {
super(parent);
}
@Override
public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec,
FilterContext context) {
return and().filter(requestSpec, responseSpec, context);
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import io.restassured.http.Cookie;
import io.restassured.http.Header;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.MultiPartSpecification;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationRequestPartFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.RequestConverter;
import org.springframework.restdocs.operation.RequestCookie;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
/**
* A converter for creating an {@link OperationRequest} from a REST Assured
* {@link FilterableRequestSpecification}.
*
* @author Andy Wilkinson
*/
class RestAssuredRequestConverter implements RequestConverter<FilterableRequestSpecification> {
@Override
public OperationRequest convert(FilterableRequestSpecification requestSpec) {
return new OperationRequestFactory().create(URI.create(requestSpec.getURI()),
HttpMethod.valueOf(requestSpec.getMethod()), extractContent(requestSpec), extractHeaders(requestSpec),
extractParameters(requestSpec), extractParts(requestSpec), extractCookies(requestSpec));
}
private Collection<RequestCookie> extractCookies(FilterableRequestSpecification requestSpec) {
Collection<RequestCookie> cookies = new ArrayList<>();
for (Cookie cookie : requestSpec.getCookies()) {
cookies.add(new RequestCookie(cookie.getName(), cookie.getValue()));
}
return cookies;
}
private byte[] extractContent(FilterableRequestSpecification requestSpec) {
return convertContent(requestSpec.getBody());
}
private byte[] convertContent(Object content) {
if (content instanceof String) {
return ((String) content).getBytes();
}
else if (content instanceof byte[]) {
return (byte[]) content;
}
else if (content instanceof File) {
return copyToByteArray((File) content);
}
else if (content instanceof InputStream) {
return copyToByteArray((InputStream) content);
}
else if (content == null) {
return new byte[0];
}
else {
throw new IllegalStateException("Unsupported request content: " + content.getClass().getName());
}
}
private byte[] copyToByteArray(File file) {
try {
return FileCopyUtils.copyToByteArray(file);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to read content from file " + file, ex);
}
}
private byte[] copyToByteArray(InputStream inputStream) {
try {
inputStream.reset();
}
catch (IOException ex) {
throw new IllegalStateException(
"Cannot read content from input stream " + inputStream + " due to reset() failure");
}
try {
return StreamUtils.copyToByteArray(inputStream);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to read content from input stream " + inputStream, ex);
}
}
private HttpHeaders extractHeaders(FilterableRequestSpecification requestSpec) {
HttpHeaders httpHeaders = new HttpHeaders();
for (Header header : requestSpec.getHeaders()) {
if (!isAllMediaTypesAcceptHeader(header)) {
httpHeaders.add(header.getName(), header.getValue());
}
}
return httpHeaders;
}
private boolean isAllMediaTypesAcceptHeader(Header header) {
return HttpHeaders.ACCEPT.equals(header.getName()) && "*/*".equals(header.getValue());
}
private Parameters extractParameters(FilterableRequestSpecification requestSpec) {
Parameters parameters = new Parameters();
for (Entry<String, ?> entry : requestSpec.getQueryParams().entrySet()) {
if (entry.getValue() instanceof Collection) {
Collection<?> queryParams = ((Collection<?>) entry.getValue());
for (Object queryParam : queryParams) {
parameters.add(entry.getKey(), queryParam.toString());
}
}
else {
parameters.add(entry.getKey(), entry.getValue().toString());
}
}
for (Entry<String, ?> entry : requestSpec.getRequestParams().entrySet()) {
parameters.add(entry.getKey(), entry.getValue().toString());
}
for (Entry<String, ?> entry : requestSpec.getFormParams().entrySet()) {
parameters.add(entry.getKey(), entry.getValue().toString());
}
return parameters;
}
private Collection<OperationRequestPart> extractParts(FilterableRequestSpecification requestSpec) {
List<OperationRequestPart> parts = new ArrayList<>();
for (MultiPartSpecification multiPartSpec : requestSpec.getMultiPartParams()) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType((multiPartSpec.getMimeType() != null)
? MediaType.parseMediaType(multiPartSpec.getMimeType()) : MediaType.TEXT_PLAIN);
parts.add(new OperationRequestPartFactory().create(multiPartSpec.getControlName(),
multiPartSpec.getFileName(), convertContent(multiPartSpec.getContent()), headers));
}
return parts;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import io.restassured.http.Header;
import io.restassured.response.Response;
import org.springframework.http.HttpHeaders;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.ResponseConverter;
/**
* A converter for creating an {@link OperationResponse} from a REST Assured
* {@link Response}.
*
* @author Andy Wilkinson
*/
class RestAssuredResponseConverter implements ResponseConverter<Response> {
@Override
public OperationResponse convert(Response response) {
return new OperationResponseFactory().create(response.getStatusCode(), extractHeaders(response),
extractContent(response));
}
private HttpHeaders extractHeaders(Response response) {
HttpHeaders httpHeaders = new HttpHeaders();
for (Header header : response.getHeaders()) {
httpHeaders.add(header.getName(), header.getValue());
}
return httpHeaders;
}
private byte[] extractContent(Response response) {
return response.getBody().asByteArray();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.snippet.Snippet;
/**
* Static factory methods for documenting RESTful APIs using REST Assured.
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public abstract class RestAssuredRestDocumentation {
private static final RestAssuredRequestConverter REQUEST_CONVERTER = new RestAssuredRequestConverter();
private static final RestAssuredResponseConverter RESPONSE_CONVERTER = new RestAssuredResponseConverter();
private RestAssuredRestDocumentation() {
}
/**
* Documents the API call with the given {@code identifier} using the given
* {@code snippets}.
* @param identifier an identifier for the API call that is being documented
* @param snippets the snippets that will document the API call
* @return a {@link RestDocumentationFilter} that will produce the documentation
*/
public static RestDocumentationFilter document(String identifier, Snippet... snippets) {
return new RestDocumentationFilter(
new RestDocumentationGenerator<>(identifier, REQUEST_CONVERTER, RESPONSE_CONVERTER, snippets));
}
/**
* Documents the API call with the given {@code identifier} using the given
* {@code snippets} in addition to any default snippets. The given
* {@code requestPreprocessor} is applied to the request before it is documented.
* @param identifier an identifier for the API call that is being documented
* @param requestPreprocessor the request preprocessor
* @param snippets the snippets
* @return a {@link RestDocumentationFilter} that will produce the documentation
*/
public static RestDocumentationFilter document(String identifier, OperationRequestPreprocessor requestPreprocessor,
Snippet... snippets) {
return new RestDocumentationFilter(new RestDocumentationGenerator<>(identifier, REQUEST_CONVERTER,
RESPONSE_CONVERTER, requestPreprocessor, snippets));
}
/**
* Documents the API call with the given {@code identifier} using the given
* {@code snippets} in addition to any default snippets. The given
* {@code responsePreprocessor} is applied to the request before it is documented.
* @param identifier an identifier for the API call that is being documented
* @param responsePreprocessor the response preprocessor
* @param snippets the snippets
* @return a {@link RestDocumentationFilter} that will produce the documentation
*/
public static RestDocumentationFilter document(String identifier,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
return new RestDocumentationFilter(new RestDocumentationGenerator<>(identifier, REQUEST_CONVERTER,
RESPONSE_CONVERTER, responsePreprocessor, snippets));
}
/**
* Documents the API call with the given {@code identifier} using the given
* {@code snippets} in addition to any default snippets. The given
* {@code requestPreprocessor} and {@code responsePreprocessor} are applied to the
* request and response respectively before they are documented.
* @param identifier an identifier for the API call that is being documented
* @param requestPreprocessor the request preprocessor
* @param responsePreprocessor the response preprocessor
* @param snippets the snippets
* @return a {@link RestDocumentationFilter} that will produce the documentation
*/
public static RestDocumentationFilter document(String identifier, OperationRequestPreprocessor requestPreprocessor,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
return new RestDocumentationFilter(new RestDocumentationGenerator<>(identifier, REQUEST_CONVERTER,
RESPONSE_CONVERTER, requestPreprocessor, responsePreprocessor, snippets));
}
/**
* Provides access to a {@link RestAssuredRestDocumentationConfigurer} that can be
* used to configure Spring REST Docs using the given {@code contextProvider}.
* @param contextProvider the context provider
* @return the configurer
*/
public static RestAssuredRestDocumentationConfigurer documentationConfiguration(
RestDocumentationContextProvider contextProvider) {
return new RestAssuredRestDocumentationConfigurer(contextProvider);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.util.HashMap;
import java.util.Map;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.config.RestDocumentationConfigurer;
/**
* A REST Assured-specific {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
* @since 1.2.0
*/
public final class RestAssuredRestDocumentationConfigurer extends
RestDocumentationConfigurer<RestAssuredSnippetConfigurer, RestAssuredOperationPreprocessorsConfigurer, RestAssuredRestDocumentationConfigurer>
implements Filter {
private final RestAssuredSnippetConfigurer snippetConfigurer = new RestAssuredSnippetConfigurer(this);
private final RestAssuredOperationPreprocessorsConfigurer operationPreprocessorsConfigurer = new RestAssuredOperationPreprocessorsConfigurer(
this);
private final RestDocumentationContextProvider contextProvider;
RestAssuredRestDocumentationConfigurer(RestDocumentationContextProvider contextProvider) {
this.contextProvider = contextProvider;
}
@Override
public RestAssuredSnippetConfigurer snippets() {
return this.snippetConfigurer;
}
@Override
public RestAssuredOperationPreprocessorsConfigurer operationPreprocessors() {
return this.operationPreprocessorsConfigurer;
}
@Override
public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec,
FilterContext filterContext) {
RestDocumentationContext context = this.contextProvider.beforeOperation();
filterContext.setValue(RestDocumentationContext.class.getName(), context);
Map<String, Object> configuration = new HashMap<>();
filterContext.setValue(RestDocumentationFilter.CONTEXT_KEY_CONFIGURATION, configuration);
apply(configuration, context);
return filterContext.next(requestSpec, responseSpec);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.springframework.restdocs.config.SnippetConfigurer;
/**
* A configurer that can be used to configure the generated documentation snippets when
* using REST Assured.
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public final class RestAssuredSnippetConfigurer extends
SnippetConfigurer<RestAssuredRestDocumentationConfigurer, RestAssuredSnippetConfigurer> implements Filter {
RestAssuredSnippetConfigurer(RestAssuredRestDocumentationConfigurer parent) {
super(parent);
}
@Override
public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec,
FilterContext context) {
return and().filter(requestSpec, responseSpec, context);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.util.HashMap;
import java.util.Map;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.util.Assert;
/**
* A REST Assured {@link Filter} for documenting RESTful APIs.
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public class RestDocumentationFilter implements Filter {
static final String CONTEXT_KEY_CONFIGURATION = "org.springframework.restdocs.configuration";
private final RestDocumentationGenerator<FilterableRequestSpecification, Response> delegate;
RestDocumentationFilter(RestDocumentationGenerator<FilterableRequestSpecification, Response> delegate) {
Assert.notNull(delegate, "delegate must be non-null");
this.delegate = delegate;
}
@Override
public final Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext context) {
Response response = context.next(requestSpec, responseSpec);
Map<String, Object> configuration = getConfiguration(requestSpec, context);
this.delegate.handle(requestSpec, response, configuration);
return response;
}
/**
* Returns the configuration that should be used when calling the delgate. The
* configuration is derived from the given {@code requestSpec} and {@code context}.
* @param requestSpec the request specification
* @param context the filter context
* @return the configuration
*/
protected Map<String, Object> getConfiguration(FilterableRequestSpecification requestSpec, FilterContext context) {
Map<String, Object> configuration = new HashMap<>(retrieveConfiguration(context));
configuration.put(RestDocumentationContext.class.getName(),
context.<RestDocumentationContext>getValue(RestDocumentationContext.class.getName()));
configuration.put(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE, requestSpec.getUserDefinedPath());
return configuration;
}
/**
* Creates a new {@link RestDocumentationFilter} that will produce documentation using
* the given {@code snippets}.
* @param snippets the snippets
* @return the new result handler
*/
public final RestDocumentationFilter document(Snippet... snippets) {
return new RestDocumentationFilter(this.delegate.withSnippets(snippets)) {
@Override
protected Map<String, Object> getConfiguration(FilterableRequestSpecification requestSpec,
FilterContext context) {
Map<String, Object> configuration = super.getConfiguration(requestSpec, context);
configuration.remove(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
configuration.remove(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR);
configuration.remove(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR);
return configuration;
}
};
}
private static Map<String, Object> retrieveConfiguration(FilterContext context) {
Map<String, Object> configuration = context.getValue(CONTEXT_KEY_CONFIGURATION);
Assert.state(configuration != null,
() -> "REST Docs configuration not found. Did you forget to add a "
+ RestAssuredRestDocumentationConfigurer.class.getSimpleName()
+ " as a filter when building the RequestSpecification?");
return configuration;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2014-2022 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
*
* https://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.
*/
/**
* Core classes for using Spring REST Docs with REST Assured.
*/
package org.springframework.restdocs.restassured;

View File

@@ -0,0 +1,295 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import io.restassured.RestAssured;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.RequestSpecification;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.RequestCookie;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RestAssuredRequestConverter}.
*
* @author Andy Wilkinson
*/
public class RestAssuredRequestConverterTests {
@ClassRule
public static TomcatServer tomcat = new TomcatServer();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final RestAssuredRequestConverter factory = new RestAssuredRequestConverter();
@Test
public void requestUri() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost:" + tomcat.getPort() + "/foo/bar"));
}
@Test
public void requestMethod() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.head("/foo/bar");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getMethod()).isEqualTo(HttpMethod.HEAD);
}
@Test
public void queryStringParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters()).hasSize(1);
assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar"));
}
@Test
public void queryStringFromUrlParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort());
requestSpec.get("/?foo=bar&foo=qix");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters()).hasSize(1);
assertThat(request.getParameters()).containsEntry("foo", Arrays.asList("bar", "qix"));
}
@Test
public void formParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).formParam("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters()).hasSize(1);
assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar"));
}
@Test
public void requestParameters() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters()).hasSize(1);
assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar"));
}
@Test
public void headers() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getHeaders()).hasSize(2);
assertThat(request.getHeaders()).containsEntry("Foo", Collections.singletonList("bar"));
assertThat(request.getHeaders()).containsEntry("Host",
Collections.singletonList("localhost:" + tomcat.getPort()));
}
@Test
public void headersWithCustomAccept() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).header("Foo", "bar")
.accept("application/json");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getHeaders()).hasSize(3);
assertThat(request.getHeaders()).containsEntry("Foo", Collections.singletonList("bar"));
assertThat(request.getHeaders()).containsEntry("Accept", Collections.singletonList("application/json"));
assertThat(request.getHeaders()).containsEntry("Host",
Collections.singletonList("localhost:" + tomcat.getPort()));
}
@Test
public void cookies() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).cookie("cookie1", "cookieVal1")
.cookie("cookie2", "cookieVal2");
requestSpec.get("/");
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getCookies().size()).isEqualTo(2);
Iterator<RequestCookie> cookieIterator = request.getCookies().iterator();
RequestCookie cookie1 = cookieIterator.next();
assertThat(cookie1.getName()).isEqualTo("cookie1");
assertThat(cookie1.getValue()).isEqualTo("cookieVal1");
RequestCookie cookie2 = cookieIterator.next();
assertThat(cookie2.getName()).isEqualTo("cookie2");
assertThat(cookie2.getValue()).isEqualTo("cookieVal2");
}
@Test
public void multipart() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort())
.multiPart("a", "a.txt", "alpha", null).multiPart("b", new ObjectBody("bar"), "application/json");
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
Collection<OperationRequestPart> parts = request.getParts();
assertThat(parts).hasSize(2);
assertThat(parts).extracting("name").containsExactly("a", "b");
assertThat(parts).extracting("submittedFileName").containsExactly("a.txt", "file");
assertThat(parts).extracting("contentAsString").containsExactly("alpha", "{\"foo\":\"bar\"}");
assertThat(parts).extracting("headers").extracting(HttpHeaders.CONTENT_TYPE).containsExactly(
Collections.singletonList(MediaType.TEXT_PLAIN_VALUE),
Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
}
@Test
public void byteArrayBody() {
RequestSpecification requestSpec = RestAssured.given().body("body".getBytes()).port(tomcat.getPort());
requestSpec.post();
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
public void stringBody() {
RequestSpecification requestSpec = RestAssured.given().body("body").port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString()).isEqualTo("body");
}
@Test
public void objectBody() {
RequestSpecification requestSpec = RestAssured.given().body(new ObjectBody("bar")).port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString()).isEqualTo("{\"foo\":\"bar\"}");
}
@Test
public void byteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given().body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 }))
.port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContent()).isEqualTo(new byte[] { 1, 2, 3, 4 });
}
@Test
public void fileBody() {
RequestSpecification requestSpec = RestAssured.given().body(new File("src/test/resources/body.txt"))
.port(tomcat.getPort());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString()).isEqualTo("file");
}
@Test
public void fileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given().body(inputStream).port(tomcat.getPort());
requestSpec.post();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Cannot read content from input stream " + inputStream + " due to reset() failure");
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
public void multipartWithByteArrayInputStreamBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("foo", "foo.txt",
new ByteArrayInputStream("foo".getBytes()));
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParts().iterator().next().getContentAsString()).isEqualTo("foo");
}
@Test
public void multipartWithStringBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "foo");
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParts().iterator().next().getContentAsString()).isEqualTo("foo");
}
@Test
public void multipartWithByteArrayBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control", "file",
"foo".getBytes());
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParts().iterator().next().getContentAsString()).isEqualTo("foo");
}
@Test
public void multipartWithFileBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort())
.multiPart(new File("src/test/resources/body.txt"));
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParts().iterator().next().getContentAsString()).isEqualTo("file");
}
@Test
public void multipartWithFileInputStreamBody() throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream("src/test/resources/body.txt");
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("foo", "foo.txt",
inputStream);
requestSpec.post();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Cannot read content from input stream " + inputStream + " due to reset() failure");
this.factory.convert((FilterableRequestSpecification) requestSpec);
}
@Test
public void multipartWithObjectBody() {
RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).multiPart("control",
new ObjectBody("bar"));
requestSpec.post();
OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec);
assertThat(request.getParts().iterator().next().getContentAsString()).isEqualTo("{\"foo\":\"bar\"}");
}
/**
* Sample object body to verify JSON serialization.
*/
public static class ObjectBody {
private final String foo;
ObjectBody(String foo) {
this.foo = foo;
}
public String getFoo() {
return this.foo;
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import io.restassured.http.Headers;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import org.junit.Test;
import org.springframework.restdocs.operation.OperationResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link RestAssuredResponseConverter}.
*
* @author Andy Wilkinson
*/
public class RestAssuredResponseConverterTests {
private final RestAssuredResponseConverter converter = new RestAssuredResponseConverter();
@Test
public void responseWithCustomStatus() {
Response response = mock(Response.class);
given(response.getStatusCode()).willReturn(600);
given(response.getHeaders()).willReturn(new Headers());
ResponseBody<?> body = mock(ResponseBody.class);
given(response.getBody()).willReturn(body);
given(body.asByteArray()).willReturn(new byte[0]);
OperationResponse operationResponse = this.converter.convert(response);
assertThat(operationResponse.getStatus()).isNull();
assertThat(operationResponse.getStatusCode()).isEqualTo(600);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.util.List;
import java.util.Map;
import io.restassured.filter.FilterContext;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RestAssuredRestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationConfigurerTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private final FilterableRequestSpecification requestSpec = mock(FilterableRequestSpecification.class);
private final FilterableResponseSpecification responseSpec = mock(FilterableResponseSpecification.class);
private final FilterContext filterContext = mock(FilterContext.class);
private final RestAssuredRestDocumentationConfigurer configurer = new RestAssuredRestDocumentationConfigurer(
this.restDocumentation);
@Test
public void nextFilterIsCalled() {
this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext);
verify(this.filterContext).next(this.requestSpec, this.responseSpec);
}
@Test
public void configurationIsAddedToTheContext() {
this.configurer.operationPreprocessors().withRequestDefaults(Preprocessors.prettyPrint())
.withResponseDefaults(Preprocessors.removeHeaders("Foo"))
.filter(this.requestSpec, this.responseSpec, this.filterContext);
@SuppressWarnings("rawtypes")
ArgumentCaptor<Map> configurationCaptor = ArgumentCaptor.forClass(Map.class);
verify(this.filterContext).setValue(eq(RestDocumentationFilter.CONTEXT_KEY_CONFIGURATION),
configurationCaptor.capture());
@SuppressWarnings("unchecked")
Map<String, Object> configuration = configurationCaptor.getValue();
assertThat(configuration.get(TemplateEngine.class.getName())).isInstanceOf(TemplateEngine.class);
assertThat(configuration.get(WriterResolver.class.getName())).isInstanceOf(WriterResolver.class);
assertThat(configuration.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS))
.isInstanceOf(List.class);
assertThat(configuration.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR))
.isInstanceOf(OperationRequestPreprocessor.class);
assertThat(configuration.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR))
.isInstanceOf(OperationResponsePreprocessor.class);
}
}

View File

@@ -0,0 +1,410 @@
/*
* Copyright 2014-2022 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
*
* https://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.restassured;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.assertj.core.api.Condition;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.testfixtures.SnippetConditions;
import org.springframework.restdocs.testfixtures.SnippetConditions.CodeBlockCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpRequestCondition;
import org.springframework.restdocs.testfixtures.SnippetConditions.HttpResponseCondition;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.maskLinks;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
/**
* Integration tests for using Spring REST Docs with REST Assured.
*
* @author Andy Wilkinson
* @author Tomasz Kopczynski
* @author Filip Hrisafov
*/
public class RestAssuredRestDocumentationIntegrationTests {
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
@ClassRule
public static TomcatServer tomcat = new TomcatServer();
@Test
public void defaultSnippetGeneration() {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("default")).get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/default"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
}
@Test
public void curlSnippetWithContent() throws Exception {
String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("curl-snippet-with-content")).accept("application/json").body("content")
.contentType(contentType).post("/").then().statusCode(200);
assertThat(new File("build/generated-snippets/curl-snippet-with-content/curl-request.adoc")).has(content(
codeBlock(TemplateFormats.asciidoctor(), "bash").withContent(String.format("$ curl 'http://localhost:"
+ tomcat.getPort() + "/' -i -X POST \\%n" + " -H 'Accept: application/json' \\%n"
+ " -H 'Content-Type: " + contentType + "' \\%n" + " -d 'content'"))));
}
@Test
public void curlSnippetWithCookies() throws Exception {
String contentType = "text/plain; charset=UTF-8";
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("curl-snippet-with-cookies")).accept("application/json").contentType(contentType)
.cookie("cookieName", "cookieVal").get("/").then().statusCode(200);
assertThat(new File("build/generated-snippets/curl-snippet-with-cookies/curl-request.adoc")).has(content(
codeBlock(TemplateFormats.asciidoctor(), "bash").withContent(String.format("$ curl 'http://localhost:"
+ tomcat.getPort() + "/' -i -X GET \\%n" + " -H 'Accept: application/json' \\%n"
+ " -H 'Content-Type: " + contentType + "' \\%n" + " --cookie 'cookieName=cookieVal'"))));
}
@Test
public void curlSnippetWithEmptyParameterQueryString() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("curl-snippet-with-empty-parameter-query-string")).accept("application/json")
.param("a", "").get("/").then().statusCode(200);
assertThat(
new File("build/generated-snippets/curl-snippet-with-empty-parameter-query-string/curl-request.adoc"))
.has(content(codeBlock(TemplateFormats.asciidoctor(), "bash")
.withContent(String.format("$ curl 'http://localhost:" + tomcat.getPort()
+ "/?a=' -i -X GET \\%n -H 'Accept: application/json'"))));
}
@Test
public void curlSnippetWithQueryStringOnPost() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("curl-snippet-with-query-string")).accept("application/json").param("foo", "bar")
.param("a", "alpha").post("/?foo=bar").then().statusCode(200);
String contentType = "application/x-www-form-urlencoded; charset=ISO-8859-1";
assertThat(new File("build/generated-snippets/curl-snippet-with-query-string/curl-request.adoc"))
.has(content(codeBlock(TemplateFormats.asciidoctor(), "bash")
.withContent(String.format("$ curl " + "'http://localhost:" + tomcat.getPort()
+ "/?foo=bar' -i -X POST \\%n" + " -H 'Accept: application/json' \\%n"
+ " -H 'Content-Type: " + contentType + "' \\%n" + " -d 'a=alpha'"))));
}
@Test
public void linksSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("links", links(linkWithRel("rel").description("The description"))))
.accept("application/json").get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "links.adoc");
}
@Test
public void pathParametersSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("path-parameters",
pathParameters(parameterWithName("foo").description("The description"))))
.accept("application/json").get("/{foo}", "").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/path-parameters"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "path-parameters.adoc");
}
@Test
public void requestParametersSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("request-parameters",
requestParameters(parameterWithName("foo").description("The description"))))
.accept("application/json").param("foo", "bar").get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/request-parameters"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "request-parameters.adoc");
}
@Test
public void requestFieldsSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("request-fields", requestFields(fieldWithPath("a").description("The description"))))
.accept("application/json").body("{\"a\":\"alpha\"}").post("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/request-fields"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "request-fields.adoc");
}
@Test
public void requestPartsSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("request-parts", requestParts(partWithName("a").description("The description"))))
.multiPart("a", "foo").post("/upload").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/request-parts"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "request-parts.adoc");
}
@Test
public void responseFieldsSnippet() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("response-fields",
responseFields(fieldWithPath("a").description("The description"),
subsectionWithPath("links").description("Links to other resources"))))
.accept("application/json").get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/response-fields"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc", "response-fields.adoc");
}
@Test
public void parameterizedOutputDirectory() throws Exception {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("{method-name}")).get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/parameterized-output-directory"),
"http-request.adoc", "http-response.adoc", "curl-request.adoc");
}
@Test
public void multiStep() throws Exception {
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(document("{method-name}-{step}")).build();
given(spec).get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/multi-step-1/"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
given(spec).get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/multi-step-2/"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
given(spec).get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/multi-step-3/"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
}
@Test
public void additionalSnippets() throws Exception {
RestDocumentationFilter documentation = document("{method-name}-{step}");
RequestSpecification spec = new RequestSpecBuilder().setPort(tomcat.getPort())
.addFilter(documentationConfiguration(this.restDocumentation)).addFilter(documentation).build();
given(spec).filter(documentation.document(
responseHeaders(headerWithName("a").description("one"), headerWithName("Foo").description("two"))))
.get("/").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/additional-snippets-1/"),
"http-request.adoc", "http-response.adoc", "curl-request.adoc", "response-headers.adoc");
}
@Test
public void responseWithCookie() {
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("set-cookie",
preprocessResponse(removeHeaders(HttpHeaders.DATE, HttpHeaders.CONTENT_TYPE))))
.get("/set-cookie").then().statusCode(200);
assertExpectedSnippetFilesExist(new File("build/generated-snippets/set-cookie"), "http-request.adoc",
"http-response.adoc", "curl-request.adoc");
assertThat(new File("build/generated-snippets/set-cookie/http-response.adoc"))
.has(content(httpResponse(TemplateFormats.asciidoctor(), HttpStatus.OK)
.header(HttpHeaders.SET_COOKIE, "name=value; Domain=localhost; HttpOnly")
.header("Keep-Alive", "timeout=60").header("Connection", "keep-alive")));
}
@Test
public void preprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation)).header("a", "alpha")
.header("b", "bravo").contentType("application/json").accept("application/json")
.body("{\"a\":\"alpha\"}").filter(document("original-request"))
.filter(document("preprocessed-request",
preprocessRequest(prettyPrint(), replacePattern(pattern, "\"<<beta>>\""),
modifyUris().removePort(), removeHeaders("a", HttpHeaders.CONTENT_LENGTH))))
.get("/").then().statusCode(200);
assertThat(new File("build/generated-snippets/original-request/http-request.adoc"))
.has(content(httpRequest(TemplateFormats.asciidoctor(), RequestMethod.GET, "/").header("a", "alpha")
.header("b", "bravo").header("Accept", MediaType.APPLICATION_JSON_VALUE)
.header("Content-Type", "application/json").header("Host", "localhost:" + tomcat.getPort())
.header("Content-Length", "13").content("{\"a\":\"alpha\"}")));
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\"%n}");
assertThat(new File("build/generated-snippets/preprocessed-request/http-request.adoc"))
.has(content(httpRequest(TemplateFormats.asciidoctor(), RequestMethod.GET, "/").header("b", "bravo")
.header("Accept", MediaType.APPLICATION_JSON_VALUE).header("Content-Type", "application/json")
.header("Host", "localhost").content(prettyPrinted)));
}
@Test
public void defaultPreprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors().withRequestDefaults(
prettyPrint(), replacePattern(pattern, "\"<<beta>>\""), modifyUris().removePort(),
removeHeaders("a", HttpHeaders.CONTENT_LENGTH)))
.header("a", "alpha").header("b", "bravo").contentType("application/json").accept("application/json")
.body("{\"a\":\"alpha\"}").filter(document("default-preprocessed-request")).get("/").then()
.statusCode(200);
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\"%n}");
assertThat(new File("build/generated-snippets/default-preprocessed-request/http-request.adoc"))
.has(content(httpRequest(TemplateFormats.asciidoctor(), RequestMethod.GET, "/").header("b", "bravo")
.header("Accept", MediaType.APPLICATION_JSON_VALUE).header("Content-Type", "application/json")
.header("Host", "localhost").content(prettyPrinted)));
}
@Test
public void preprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation))
.filter(document("original-response"))
.filter(document("preprocessed-response",
preprocessResponse(prettyPrint(), maskLinks(),
removeHeaders("a", "Transfer-Encoding", "Date", "Server"),
replacePattern(pattern, "\"<<beta>>\""),
modifyUris().scheme("https").host("api.example.com").removePort())))
.get("/").then().statusCode(200);
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\",%n \"links\" : "
+ "[ {%n \"rel\" : \"rel\",%n \"href\" : \"...\"%n } ]%n}");
assertThat(new File("build/generated-snippets/preprocessed-response/http-response.adoc"))
.has(content(httpResponse(TemplateFormats.asciidoctor(), HttpStatus.OK)
.header("Foo", "https://api.example.com/foo/bar")
.header("Content-Type", "application/json;charset=UTF-8").header("Keep-Alive", "timeout=60")
.header("Connection", "keep-alive")
.header(HttpHeaders.CONTENT_LENGTH, prettyPrinted.getBytes().length).content(prettyPrinted)));
}
@Test
public void defaultPreprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(tomcat.getPort())
.filter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withResponseDefaults(prettyPrint(), maskLinks(),
removeHeaders("a", "Transfer-Encoding", "Date", "Server"),
replacePattern(pattern, "\"<<beta>>\""),
modifyUris().scheme("https").host("api.example.com").removePort()))
.filter(document("default-preprocessed-response")).get("/").then().statusCode(200);
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\",%n \"links\" : "
+ "[ {%n \"rel\" : \"rel\",%n \"href\" : \"...\"%n } ]%n}");
assertThat(new File("build/generated-snippets/default-preprocessed-response/http-response.adoc"))
.has(content(httpResponse(TemplateFormats.asciidoctor(), HttpStatus.OK)
.header("Foo", "https://api.example.com/foo/bar")
.header("Content-Type", "application/json;charset=UTF-8").header("Keep-Alive", "timeout=60")
.header("Connection", "keep-alive")
.header(HttpHeaders.CONTENT_LENGTH, prettyPrinted.getBytes().length).content(prettyPrinted)));
}
@Test
public void customSnippetTemplate() throws Exception {
ClassLoader classLoader = new URLClassLoader(
new URL[] { new File("src/test/resources/custom-snippet-templates").toURI().toURL() },
getClass().getClassLoader());
ClassLoader previous = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
given().port(tomcat.getPort()).accept("application/json")
.filter(documentationConfiguration(this.restDocumentation))
.filter(document("custom-snippet-template")).get("/").then().statusCode(200);
}
finally {
Thread.currentThread().setContextClassLoader(previous);
}
assertThat(new File("build/generated-snippets/custom-snippet-template/curl-request.adoc"))
.hasContent("Custom curl request");
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentRequestSpecificationNotConfigured() {
assertThatThrownBy(() -> given().port(tomcat.getPort()).filter(document("default")).get("/"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("REST Docs configuration not found. Did you forget to add a "
+ "RestAssuredRestDocumentationConfigurer as a filter when building the RequestSpecification?");
}
@Test
public void exceptionShouldBeThrownWhenCallDocumentSnippetsRequestSpecificationNotConfigured() {
RestDocumentationFilter documentation = document("{method-name}-{step}");
assertThatThrownBy(() -> given().port(tomcat.getPort())
.filter(documentation.document(responseHeaders(headerWithName("a").description("one")))).get("/"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("REST Docs configuration not found. Did you forget to add a "
+ "RestAssuredRestDocumentationConfigurer as a filter when building the "
+ "RequestSpecification?");
}
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
for (String snippet : snippets) {
assertThat(new File(directory, snippet)).isFile();
}
}
private Condition<File> content(final Condition<String> delegate) {
return new Condition<File>() {
@Override
public boolean matches(File value) {
try {
return delegate.matches(FileCopyUtils
.copyToString(new InputStreamReader(new FileInputStream(value), StandardCharsets.UTF_8)));
}
catch (IOException ex) {
fail("Failed to read '" + value + "'", ex);
return false;
}
}
};
}
private CodeBlockCondition<?> codeBlock(TemplateFormat format, String language) {
return SnippetConditions.codeBlock(format, language);
}
private HttpRequestCondition httpRequest(TemplateFormat format, RequestMethod requestMethod, String uri) {
return SnippetConditions.httpRequest(format, requestMethod, uri);
}
private HttpResponseCondition httpResponse(TemplateFormat format, HttpStatus status) {
return SnippetConditions.httpResponse(format, status);
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2014-20212the 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
*
* https://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.restassured;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.junit.rules.ExternalResource;
/**
* {@link ExternalResource} that starts and stops a Tomcat server.
*
* @author Andy Wilkinson
*/
class TomcatServer extends ExternalResource {
private Tomcat tomcat;
private int port;
@Override
protected void before() throws LifecycleException {
this.tomcat = new Tomcat();
this.tomcat.getConnector().setPort(0);
Context context = this.tomcat.addContext("/", null);
this.tomcat.addServlet("/", "test", new TestServlet());
context.addServletMappingDecoded("/", "test");
this.tomcat.addServlet("/", "set-cookie", new CookiesServlet());
context.addServletMappingDecoded("/set-cookie", "set-cookie");
this.tomcat.start();
this.port = this.tomcat.getConnector().getLocalPort();
}
@Override
protected void after() {
try {
this.tomcat.stop();
}
catch (LifecycleException ex) {
throw new RuntimeException(ex);
}
}
int getPort() {
return this.port;
}
/**
* {@link HttpServlet} used to handle requests in the tests.
*/
private static final class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
respondWithJson(response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
respondWithJson(response);
}
private void respondWithJson(HttpServletResponse response) throws IOException, JsonProcessingException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
Map<String, Object> content = new HashMap<>();
content.put("a", "alpha");
Map<String, String> link = new HashMap<>();
link.put("rel", "rel");
link.put("href", "href");
content.put("links", Arrays.asList(link));
response.getWriter().println(new ObjectMapper().writeValueAsString(content));
response.setHeader("a", "alpha");
response.setHeader("Foo", "http://localhost:12345/foo/bar");
response.flushBuffer();
}
}
/**
* {@link HttpServlet} used to handle cookies-related requests in the tests.
*/
private static final class CookiesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("name", "value");
cookie.setDomain("localhost");
cookie.setHttpOnly(true);
resp.addCookie(cookie);
}
}
}

View File

@@ -0,0 +1 @@
file