Add support for using REST Assured to generate documentation snippets

This commit adds a new module, spring-restdocs-restassured, that
can be used to generate documentation snippets when testing a service
with REST Assured.

Please refer to the updated reference documentation for details.

Thanks to Johan Haleby for making a change to REST Assured so that
path parameters could be documented.

Closes gh-102
This commit is contained in:
Andy Wilkinson
2015-09-07 14:36:42 +01:00
parent f73108ae36
commit 130b411e2a
66 changed files with 3361 additions and 433 deletions

View File

@@ -58,6 +58,7 @@ subprojects {
}
dependencies {
dependency 'com.fasterxml.jackson.core:jackson-databind:2.4.6.1'
dependency 'com.jayway.restassured:rest-assured:2.8.0'
dependency 'com.samskivert:jmustache:1.11'
dependency 'commons-codec:commons-codec:1.10'
dependency 'javax.servlet:javax.servlet-api:3.1.0'

View File

@@ -73,7 +73,7 @@
<module name="AvoidStarImport" />
<module name="AvoidStaticImport">
<property name="excludes"
value="org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.curl.CurlDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
value="com.jayway.restassured.RestAssured.*, org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.curl.CurlDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
</module>
<module name="IllegalImport" />
<module name="RedundantImport" />

View File

@@ -4,6 +4,7 @@ plugins {
dependencies {
testCompile project(':spring-restdocs-mockmvc')
testCompile project(':spring-restdocs-restassured')
testCompile 'javax.validation:validation-api'
}

View File

@@ -6,7 +6,12 @@
[[configuration-uris]]
=== Documented URIs
The default configuration for URIs documented by Spring REST Docs is:
NOTE: As REST Assured tests a service by making actual HTTP requests, the documented
URIs cannot be customized in this way. You should use the
<<customizing-requests-and-responses-preprocessors-modify-uris, REST-Assured specific
preprocessor>> instead.
When using MockMvc, the default configuration for URIs documented by Spring REST Docs is:
|===
|Setting |Default
@@ -21,12 +26,12 @@ The default configuration for URIs documented by Spring REST Docs is:
|`8080`
|===
This configuration is applied by `RestDocumentationMockMvcConfigurer`. You can use its API
This configuration is applied by `MockMvcRestDocumentationConfigurer`. You can use its API
to change one or more of the defaults to suit your needs:
[source,java,indent=0]
----
include::{examples-dir}/com/example/CustomUriConfiguration.java[tags=custom-uri-configuration]
include::{examples-dir}/com/example/mockmvc/CustomUriConfiguration.java[tags=custom-uri-configuration]
----
TIP: To configure a request's context path, use the `contextPath` method on
@@ -38,12 +43,19 @@ TIP: To configure a request's context path, use the `contextPath` method on
=== Snippet encoding
The default encoding used by Asciidoctor is `UTF-8`. Spring REST Docs adopts the same
default for the snippets that it generates. If you require an encoding other than `UTF-8`,
use `RestDocumentationMockMvcConfigurer` to configure it:
default for the snippets that it generates. You can change the default snippet encoding
using the `RestDocumentationConfigurer` API. For example, to use `ISO-8859-1`:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/CustomEncoding.java[tags=custom-encoding]
include::{examples-dir}/com/example/mockmvc/CustomEncoding.java[tags=custom-encoding]
----
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/CustomEncoding.java[tags=custom-encoding]
----
@@ -57,11 +69,18 @@ Three snippets are produced by default:
- `http-request`
- `http-response`
This default configuration is applied by `RestDocumentationMockMvcConfigurer`. You can use
its API to change the configuration. For example, to only produce the `curl-request`
You can change the default snippet configuration during setup using the
`RestDocumentationConfigurer` API. For example, to only produce the `curl-request`
snippet by default:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/CustomDefaultSnippetsConfiguration.java[tags=custom-default-snippets]
include::{examples-dir}/com/example/mockmvc/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]
----

View File

@@ -10,9 +10,18 @@ and/or an `OperationResponsePreprocessor`. Instances can be obtained using the
static `preprocessRequest` and `preprocessResponse` methods on `Preprocessors`. For
example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/PerTestPreprocessing.java[tags=preprocessing]
include::{examples-dir}/com/example/mockmvc/PerTestPreprocessing.java[tags=preprocessing]
----
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/PerTestPreprocessing.java[tags=preprocessing]
----
<1> Apply a request preprocessor that will remove the header named `Foo`.
<2> Apply a response preprocessor that will pretty print its content.
@@ -22,25 +31,44 @@ so by configuring the preprocessors in your `@Before` method and using the
<<documentating-your-api-parameterized-output-directories, support for parameterized
output directories>>:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/EveryTestPreprocessing.java[tags=setup]
include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=setup]
----
<1> Create the `RestDocumentationResultHandler`, configured to preprocess the request
<1> Create a `RestDocumentationResultHandler`, configured to preprocess the request
and response.
<2> Create the `MockMvc` instance, configured to always call the documentation result
<2> Create a `MockMvc` instance, configured to always call the documentation result
handler.
Then, in each test, the `RestDocumentationResultHandler` can be configured with anything
test-specific. For example:
[source,java,indent=0]
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/EveryTestPreprocessing.java[tags=use]
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=setup]
----
<1> Create a `RestDocumentationFilter`, configured to preprocess the request
and response.
<2> Create a `RequestSpecification` instance, configured to always call the documentation
filter.
Then, in each test, any configuration specific to that test can be performed. For example:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/EveryTestPreprocessing.java[tags=use]
----
<1> Document the links specific to the resource that is being tested
<2> The `perform` call will automatically produce the documentation snippets due to the
use of `alwaysDo` above.
<2> The request and response will be preprocessed due to the use of `alwaysDo` above.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/EveryTestPreprocessing.java[tags=use]
----
<1> Document the links specific to the resource that is being tested
<2> The request and response will be preprocessed due to the configuration of the
`RequestSpecification` in the `setUp` method.
Various built in preprocessors, including those illustrated above, are available via the
static methods on `Preprocessors`. See <<Preprocessors, below>> for further details.
@@ -86,6 +114,20 @@ from the request or response.
replacing content in a request or response. Any occurrences of a regular expression are
replaced.
[[customizing-requests-and-responses-preprocessors-modify-uris]]
==== Modifying URIs
TIP: If you are using MockMvc, URIs should be customized by <<configuration-uris, changing
the configuration>>.
`modifyUris` on `RestAssuredPreprocessors` can be used to modify any URIs in a request
or a response. When using REST Assured, this allows you to customize the URIs that appear
in the documentation while testing a local instance of the service.
[[customizing-requests-and-responses-preprocessors-writing-your-own]]
==== Writing your own preprocessor

View File

@@ -11,9 +11,22 @@ This section provides more details about using Spring REST Docs to document your
Spring REST Docs provides support for documenting the links in a
https://en.wikipedia.org/wiki/HATEOAS[Hypermedia-based] API:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/Hypermedia.java[tag=links]
include::{examples-dir}/com/example/mockmvc/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`.
[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
@@ -47,14 +60,23 @@ Two link formats are understood by default:
If you are using Atom or HAL-format links but with a different content type you can
provide one of the built-in `LinkExtractor` implementations to `links`. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/Hypermedia.java[tag=explicit-extractor]
include::{examples-dir}/com/example/mockmvc/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
[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.
@@ -65,9 +87,22 @@ In addition to the hypermedia-specific support <<documenting-your-api-hypermedia
above>>, support for general documentation of request and response payloads is also
provided. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/Payload.java[tags=response]
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=response]
----
<1> Configure Spring REST docs to produce a snippet describing the fields in the response
payload. To document a request `requestFields` can be used. Both are static methods on
`org.springframework.restdocs.payload.PayloadDocumentation`.
<2> Expect a field with the path `contact`. Uses the static `fieldWithPath` method on
`org.springframework.restdocs.payload.PayloadDocumentation`.
<3> Expect a field with the path `contact.email`.
[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 `requestFields` can be used. Both are static methods on
@@ -223,9 +258,17 @@ The type can also be set explicitly using the `type(Object)` method on
in the documentation. Typically, one of the values enumerated by `JsonFieldType` will be
used:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/Payload.java[tags=explicit-type]
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=explicit-type]
----
<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`.
@@ -255,9 +298,10 @@ method will be used in the documentation.
A request's parameters can be documented using `requestParameters`. Request parameters
can be included in a `GET` request's query string. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/RequestParameters.java[tags=request-parameters-query-string]
include::{examples-dir}/com/example/mockmvc/RequestParameters.java[tags=request-parameters-query-string]
----
<1> Perform a `GET` request with two parameters, `page` and `per_page` in the query
string.
@@ -268,14 +312,37 @@ include::{examples-dir}/com/example/RequestParameters.java[tags=request-paramete
`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.
Request parameters can also be included as form data in the body of a POST request:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/RequestParameters.java[tags=request-parameters-form-data]
include::{examples-dir}/com/example/mockmvc/RequestParameters.java[tags=request-parameters-form-data]
----
<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 both cases, the result is a snippet named `request-parameters.adoc` that contains a
table describing the parameters that are supported by the resource.
@@ -294,9 +361,10 @@ above.
A request's path parameters can be documented using `pathParameters`. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters]
include::{examples-dir}/com/example/mockmvc/PathParameters.java[tags=path-parameters]
----
<1> Perform a `GET` request with two path parameters, `latitude` and `longitude`.
<2> Configure Spring REST Docs to produce a snippet describing the request's path
@@ -306,6 +374,19 @@ include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters]
`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.
@@ -329,9 +410,10 @@ above.
The headers in a request or response can be documented using `requestHeaders` and
`responseHeaders` respectively. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/HttpHeaders.java[tags=headers]
include::{examples-dir}/com/example/mockmvc/HttpHeaders.java[tags=headers]
----
<1> Perform a `GET` request with an `Authorization` header that uses basic authentication
<2> Configure Spring REST Docs to produce a snippet describing the request's headers.
@@ -342,6 +424,20 @@ include::{examples-dir}/com/example/HttpHeaders.java[tags=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.
@@ -436,8 +532,7 @@ class in the Spring HATEOAS-based sample illustrates the latter approach.
[[documenting-your-api-default-snippets]]
=== Default snippets
A number of snippets are produced automatically when you document a call to
`MockMvc.perform`:
A number of snippets are produced automatically when you document a request and response.
[cols="1,3"]
|===
@@ -489,23 +584,30 @@ are supported:
| The nome of the test class, formatted using snake_case
| {step}
| The count of calls to MockMvc.perform in the current test
| The count of calls made to the service in the current test
|===
For example, `document("{class-name}/{method-name}")` in a test method named
`creatingANote` on the test class `GettingStartedDocumentation`, will write
snippets into a directory named `getting-started-documentation/creating-a-note`.
A parameterized output directory is particularly useful in combination with Spring MVC
Test's `alwaysDo` functionality. It allows documentation to be configured once in a setup
method:
A parameterized output directory is particularly useful in combination with an `@Before`
method. It allows documentation to be configured once in a setup method and then reused
in every test in the class:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/AlwaysDo.java[tags=always-do]
include::{examples-dir}/com/example/mockmvc/ParameterizedOutput.java[tags=parameterized-output]
----
With this configuration in place, every call to `MockMvc.perform` will produce
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/ParameterizedOutput.java[tags=parameterized-output]
----
With this configuration in place, every call to the service you are testing will produce
the <<documenting-your-api-default-snippets,default snippets>> without any further
configuration. Take a look at the `GettingStartedDocumentation` classes in each of the
sample applications to see this functionality in action.
@@ -549,9 +651,19 @@ A concrete example of the above is the addition of a constraints column and a ti
documenting request fields. The first step is to provide a `constraints` attribute for
each field that you are documenting and to provide a `title` attribute:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/Payload.java[tags=constraints]
include::{examples-dir}/com/example/mockmvc/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
[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

View File

@@ -83,7 +83,9 @@ the configuration are described below.
</plugins>
</build>
----
<1> Add a dependency on `spring-restdocs-mockmvc` in the `test` scope.
<1> Add a dependency on `spring-restdocs-mockmvc` in the `test` scope. If you want to use
REST Assured rather than MockMvc, add a dependency on `spring-restdocs-restassured`
instead.
<2> Configure a property to define the output location for generated snippets.
<3> Add the SureFire plugin and configure it to include files whose names end with
`Documentation.java`.
@@ -120,7 +122,9 @@ project's jar you should use the `prepare-package` phase.
}
----
<1> Apply the Asciidoctor plugin.
<2> Add a dependency on `spring-restdocs-mockmvc` in the `testCompile` configuration.
<2> Add a dependency on `spring-restdocs-mockmvc` in the `testCompile` configuration. If
you want to use REST Assured rather than MockMvc, add a dependency on
`spring-restdocs-restassured` instead.
<3> Configure a property to define the output location for generated snippets.
<4> Configure the `test` task to add the snippets directory as an output.
<5> Configure the `asciidoctor` task
@@ -199,14 +203,16 @@ from where it will be included in the jar file.
[[getting-started-documentation-snippets]]
=== Generating documentation snippets
Spring REST Docs uses {spring-framework-docs}/#spring-mvc-test-framework[Spring's MVC Test
framework] to make requests to the service that you are documenting. It then produces
documentation snippets for request and resulting response.
Spring REST Docs uses JUnit and
{spring-framework-docs}/#spring-mvc-test-framework[Spring's MVC Test framework] or
http://www.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.
[[getting-started-documentation-snippets-setup]]
==== Setting up Spring MVC test
==== Setting up your tests
The first step in generating documentation snippets is to declare a `public`
`RestDocumentation` field that's annotated as a JUnit `@Rule`. The `RestDocumentation`
@@ -214,47 +220,59 @@ rule is configured with the output directory into which generated snippets shoul
written. This output directory should match the snippets directory that you have
configured in your `build.gradle` or `pom.xml` file.
For Maven (`pom.xml` that will typically be `target/generated-snippets`:
For Maven (`pom.xml` that will typically be `target/generated-snippets` and for
Gradle (`build.gradle`) it will typically be `build/generated-snippets`:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.Maven
----
@Rule
public RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
----
And for Gradle (`build.gradle`) it will typically be `build/generated-snippets`:
[source,java,indent=0]
[source,java,indent=0,role="secondary"]
.Gradle
----
@Rule
public RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");
----
Next, provide an `@Before` method that creates a `MockMvc` instance:
Next, provide an `@Before` method to configure MockMvc or REST Assured:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/ExampleApplicationTests.java[tags=mock-mvc-setup]
include::{examples-dir}/com/example/mockmvc/ExampleApplicationTests.java[tags=setup]
----
The `MockMvc` instance is configured using a `RestDocumentationMockMvcConfigurer`. An
<1> The `MockMvc` instance is configured using a `MockMvcRestDocumentationConfigurer`. An
instance of this class can be obtained from the static `documentationConfiguration()`
method on `org.springframework.restdocs.mockmvc.MockMvcRestDocumentation`.
`RestDocumentationMockMvcConfigurer` applies sensible defaults and also provides an API
for customizing the configuration. Refer to the
<<configuration, configuration section>> for more information.
[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`. An instance of this class can be obtained from the static
`documentationConfiguration()` method on
`org.springframework.restdocs.restassured.RestAssuredRestDocumentation`.
The configurer applies sensible defaults and also provides an API for customizing the
configuration. Refer to the <<configuration, configuration section>> for more information.
[[getting-started-documentation-snippets-invoking-the-service]]
==== Invoking the RESTful service
Now that a `MockMvc` instance has been created, it can be used to invoke the RESTful
Now that the testing framework has been configured, it can be used to invoke the RESTful
service and document the request and response. For example:
[source,java,indent=0]
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/InvokeService.java[tags=invoke-service]
include::{examples-dir}/com/example/mockmvc/InvokeService.java[tags=invoke-service]
----
<1> Invoke the root (`/`) of the service and indicate that an `application/json` response
is required.
@@ -265,6 +283,21 @@ a `RestDocumentationResultHandler`. An instance of this class can be obtained fr
static `document` method on
`org.springframework.restdocs.mockmvc.MockMvcRestDocumentation`.
[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 initialised 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`
that will be located beneath the configured output directory. The snippets are written by
a `RestDocumentationFilter`. An instance of this class can be obtained from the
static `document` method on
`org.springframework.restdocs.restassured.RestAssuredRestDocumentation`.
<4> Invoke the root (`/`) of the service.
<5> Assert that the service produce the expected response.
By default, three snippets are written:
* `<output-directory>/index/curl-request.adoc`

View File

@@ -10,9 +10,10 @@ http://asciidoctor.org[Asciidoctor]. Asciidoctor processes plain text and produc
HTML, styled and layed out to suit your needs.
Spring REST Docs makes use of snippets produced by tests written with
{spring-framework-docs}/#spring-mvc-test-framework[Spring MVC Test]. This test-driven
approach helps to guarantee the accuracy of your service's documentation. If a snippet is
incorrect the test that produces it will fail.
{spring-framework-docs}/#spring-mvc-test-framework[Spring MVC Test] or
http://www.rest-assured.io[REST Assured]. This test-driven approach helps to guarantee the
accuracy of your service's documentation. If a snippet is incorrect the test that produces
it will fail.
Documenting a RESTful service is largely about describing its resources. Two key parts
of each resource's description are the details of the HTTP requests that it consumes

View File

@@ -41,6 +41,7 @@ public class Constraints {
@NotNull
@Size(min = 8)
String password;
}
// end::constraints[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
@@ -27,7 +27,7 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
public class CustomDefaultSnippetsConfiguration {
public class CustomDefaultSnippets {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
@@ -41,8 +41,8 @@ public class CustomDefaultSnippetsConfiguration {
public void setUp() {
// tag::custom-default-snippets[]
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
@@ -27,7 +27,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
public class CustomEncoding {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
@@ -40,8 +40,8 @@ public class CustomEncoding {
public void setUp() {
// tag::custom-encoding[]
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).snippets()
.withEncoding("ISO-8859-1"))
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withEncoding("ISO-8859-1"))
.build();
// end::custom-encoding[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
@@ -27,7 +27,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
public class CustomUriConfiguration {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");

View File

@@ -14,9 +14,11 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -25,6 +27,7 @@ import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
@@ -34,20 +37,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class EveryTestPreprocessing {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation(
"target/generated-snippets");
private WebApplicationContext context;
// tag::setup[]
private MockMvc mockMvc;
private RestDocumentationResultHandler document;
// tag::setup[]
@Before
public void setup() {
this.document = document(
"{method-name}", // <1>
this.document = document("{method-name}", // <1>
preprocessRequest(removeHeaders("Foo")),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(this.document) // <2>
.build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
@@ -29,8 +29,9 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.docu
public class ExampleApplicationTests {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
// tag::mock-mvc-setup[]
public final RestDocumentation restDocumentation = new RestDocumentation(
"target/generated-snippets");
// tag::setup[]
@Autowired
private WebApplicationContext context;
@@ -39,8 +40,8 @@ public class ExampleApplicationTests {
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.apply(documentationConfiguration(this.restDocumentation)) // <1>
.build();
}
// end::mock-mvc-setup[]
// end::setup[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import org.springframework.test.web.servlet.MockMvc;
@@ -32,19 +32,19 @@ public class HttpHeaders {
public void headers() throws Exception {
// tag::headers[]
this.mockMvc
.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) // <1>
.andExpect(status().isOk())
.andDo(document("headers",
requestHeaders( // <2>
headerWithName("Authorization").description(
"Basic auth credentials")), // <3>
responseHeaders( // <4>
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"))));
.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) // <1>
.andExpect(status().isOk())
.andDo(document("headers",
requestHeaders( // <2>
headerWithName("Authorization").description(
"Basic auth credentials")), // <3>
responseHeaders( // <4>
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"))));
// end::headers[]
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,10 +14,7 @@
* limitations under the License.
*/
package com.example;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
@@ -26,24 +23,25 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
public class AlwaysDo {
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
public class ParameterizedOutput {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
private MockMvc mockMvc;
private WebApplicationContext context;
// tag::always-do[]
// tag::parameterized-output[]
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}/{step}/"))
.build();
.alwaysDo(document("{method-name}/{step}/")).build();
}
// end::always-do[]
// end::parameterized-output[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
@@ -51,7 +51,6 @@ private MockMvc mockMvc;
.andDo(document("index", responseFields(
fieldWithPath("contact.email")
.type(JsonFieldType.STRING) // <1>
.optional()
.description("The user's email address"))));
// end::explicit-type[]
}
@@ -61,16 +60,13 @@ private MockMvc mockMvc;
.andExpect(status().isOk())
// tag::constraints[]
.andDo(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>
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[]
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import org.springframework.test.web.servlet.MockMvc;
@@ -33,8 +33,8 @@ public class PerTestPreprocessing {
public void general() throws Exception {
// tag::preprocessing[]
this.mockMvc.perform(get("/")).andExpect(status().isOk())
.andDo(document("index", preprocessRequest(removeHeaders("Foo")), // <1>
preprocessResponse(prettyPrint()))); // <2>
.andDo(document("index", preprocessRequest(removeHeaders("Foo")), // <1>
preprocessResponse(prettyPrint()))); // <2>
// end::preprocessing[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.example;
package com.example.mockmvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
@@ -39,7 +39,7 @@ public class RequestParameters {
)));
// end::request-parameters-query-string[]
}
public void postFormDataSnippet() throws Exception {
// tag::request-parameters-form-data[]
this.mockMvc.perform(post("/users").param("username", "Tester")) // <1>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
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-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomEncoding {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
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,72 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.restassured.RestDocumentationFilter;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
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.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;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class EveryTestPreprocessing {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation(
"target/generated-snippets");
// tag::setup[]
private RequestSpecification spec;
private RestDocumentationFilter document;
@Before
public void setup() {
this.document = document("{method-name}",
preprocessRequest(removeHeaders("Foo")),
preprocessResponse(prettyPrint())); // <1>
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(this.document)// <2>
.build();
}
// end::setup[]
public void use() throws Exception {
// tag::use[]
this.document.snippets( // <1>
links(linkWithRel("self").description("Canonical self link")));
RestAssured.given(this.spec) // <2>
.when().get("/")
.then().assertThat().statusCode(is(200));
// end::use[]
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class ExampleApplicationTests {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation(
"build/generated-snippets");
// 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,51 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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("Authroization", "Basic dXNlcjpzZWNyZXQ=") // <4>
.when().get("/people")
.then().assertThat().statusCode(is(200));
// end::headers[]
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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,38 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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,46 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.RestDocumentation;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class ParameterizedOutput {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation(
"build/generated-snippets");
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-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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,75 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import org.springframework.restdocs.payload.JsonFieldType;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
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.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").description("The user's contact details"), // <2>
fieldWithPath("contact.email").description("The user's email address")))) // <3>
.when().get("/user/5")
.then().assertThat().statusCode(is(200));
// end::response[]
}
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));
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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,53 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.restassured;
import com.jayway.restassured.RestAssured;
import com.jayway.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

@@ -2,4 +2,5 @@ rootProject.name = 'spring-restdocs'
include 'docs'
include 'spring-restdocs-core'
include 'spring-restdocs-mockmvc'
include 'spring-restdocs-mockmvc'
include 'spring-restdocs-restassured'

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
package org.springframework.restdocs.config;
import org.springframework.mock.web.MockHttpServletRequest;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
/**
* Abstract configurer that declares methods that are internal to the documentation
@@ -24,13 +26,15 @@ import org.springframework.mock.web.MockHttpServletRequest;
*
* @author Andy Wilkinson
*/
abstract class AbstractConfigurer {
public abstract class AbstractConfigurer {
/**
* Applies the configuration, possibly by modifying the given {@code request}.
* Applies the configurer to the given {@code configuration}.
*
* @param request the request that may be modified
* @param configuration the configuration to be configured
* @param context the current documentation context
*/
abstract void apply(MockHttpServletRequest request);
public abstract void apply(Map<String, Object> configuration,
RestDocumentationContext context);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.config;
/**
* Base class for {@link NestedConfigurer} implementations.
*
* @param <P> The type of the configurer's parent
* @author Andy Wilkinson
*/
public abstract class AbstractNestedConfigurer<P> extends AbstractConfigurer implements
NestedConfigurer<P> {
private final P parent;
/**
* Creates a new {@code AbstractNestedConfigurer} with the given {@code parent}.
* @param parent the parent
*/
protected AbstractNestedConfigurer(P parent) {
this.parent = parent;
}
@Override
public final P and() {
return this.parent;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,22 +14,20 @@
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
package org.springframework.restdocs.config;
/**
* A configurer that is nested and, therefore, has a parent.
*
* @param <PARENT> The parent's type
* @param <P> The parent's type
* @author Andy Wilkinson
*/
interface NestedConfigurer<PARENT extends MockMvcConfigurer> {
interface NestedConfigurer<P> {
/**
* Returns the configurer's parent.
*
* @return the parent
*/
PARENT and();
P and();
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.config;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
/**
* Abstract base class for the configuration of Spring REST Docs.
*
* @param <S> The concrete type of the {@link SnippetConfigurer}.
* @param <T> The concrete type of this configurer, to be returned from methods that
* support chaining
* @author Andy Wilkinson
*/
public abstract class RestDocumentationConfigurer<S, T> {
private final WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer();
private final TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer();
/**
* Returns a {@link SnippetConfigurer} that can be used to configure the snippets that
* will be generated.
*
* @return the snippet configurer
*/
public abstract S snippets();
/**
* Configures the {@link TemplateEngine} that will be used for snippet rendering.
*
* @param templateEngine the template engine to use
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public final T templateEngine(TemplateEngine templateEngine) {
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
return (T) this;
}
/**
* Configures the {@link WriterResolver} that will be used to resolve a writer for a
* snippet.
*
* @param writerResolver The writer resolver to use
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public final T writerResolver(WriterResolver writerResolver) {
this.writerResolverConfigurer.setWriterResolver(writerResolver);
return (T) this;
}
/**
* Returns the configurer used to configure the context with a {@link WriterResolver}.
*
* @return the configurer
*/
protected final AbstractConfigurer getWriterResolverConfigurer() {
return this.writerResolverConfigurer;
}
/**
* Returns the configurer used to configure the context with a {@link TemplateEngine}.
*
* @return the configurer
*/
protected final AbstractConfigurer getTemplateEngineConfigurer() {
return this.templateEngineConfigurer;
}
private static final class TemplateEngineConfigurer extends AbstractConfigurer {
private TemplateEngine templateEngine = new MustacheTemplateEngine(
new StandardTemplateResourceResolver());
@Override
public void apply(Map<String, Object> configuration,
RestDocumentationContext context) {
configuration.put(TemplateEngine.class.getName(), this.templateEngine);
}
private void setTemplateEngine(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
}
private static final class WriterResolverConfigurer extends AbstractConfigurer {
private WriterResolver writerResolver;
@Override
public void apply(Map<String, Object> configuration,
RestDocumentationContext context) {
WriterResolver resolverToUse = this.writerResolver;
if (resolverToUse == null) {
resolverToUse = new StandardWriterResolver(
new RestDocumentationContextPlaceholderResolver(context));
}
configuration.put(WriterResolver.class.getName(), resolverToUse);
}
private void setWriterResolver(WriterResolver writerResolver) {
this.writerResolver = writerResolver;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
package org.springframework.restdocs.config;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.curl.CurlDocumentation;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.snippet.Snippet;
@@ -28,10 +29,16 @@ import org.springframework.restdocs.snippet.WriterResolver;
/**
* A configurer that can be used to configure the generated documentation snippets.
*
* @param <P> The type of the configurer's parent
* @param <T> The concrete type of the configurer to be returned from chained methods
* @author Andy Wilkinson
*/
public class SnippetConfigurer extends
AbstractNestedConfigurer<RestDocumentationMockMvcConfigurer> {
public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P> {
/**
* The name of the attribute that is used to hold the default snippets.
*/
public static final String ATTRIBUTE_DEFAULT_SNIPPETS = "org.springframework.restdocs.defaultSnippets";
private List<Snippet> defaultSnippets = Arrays.asList(
CurlDocumentation.curlRequest(), HttpDocumentation.httpRequest(),
@@ -46,10 +53,22 @@ public class SnippetConfigurer extends
private String snippetEncoding = DEFAULT_SNIPPET_ENCODING;
SnippetConfigurer(RestDocumentationMockMvcConfigurer parent) {
/**
* Creates a new {@code SnippetConfigurer} with the given {@code parent}.
*
* @param parent the parent
*/
protected SnippetConfigurer(P parent) {
super(parent);
}
@Override
public void apply(Map<String, Object> configuration, RestDocumentationContext context) {
((WriterResolver) configuration.get(WriterResolver.class.getName()))
.setEncoding(this.snippetEncoding);
configuration.put(ATTRIBUTE_DEFAULT_SNIPPETS, this.defaultSnippets);
}
/**
* Configures any documentation snippets to be written using the given
* {@code encoding}. The default is UTF-8.
@@ -57,17 +76,10 @@ public class SnippetConfigurer extends
* @param encoding the encoding
* @return {@code this}
*/
public SnippetConfigurer withEncoding(String encoding) {
@SuppressWarnings("unchecked")
public T withEncoding(String encoding) {
this.snippetEncoding = encoding;
return this;
}
@Override
void apply(MockHttpServletRequest request) {
((WriterResolver) request.getAttribute(WriterResolver.class.getName()))
.setEncoding(this.snippetEncoding);
request.setAttribute("org.springframework.restdocs.mockmvc.defaultSnippets",
this.defaultSnippets);
return (T) this;
}
/**
@@ -76,8 +88,9 @@ public class SnippetConfigurer extends
* @param defaultSnippets the default snippets
* @return {@code this}
*/
public SnippetConfigurer withDefaults(Snippet... defaultSnippets) {
@SuppressWarnings("unchecked")
public T withDefaults(Snippet... defaultSnippets) {
this.defaultSnippets = Arrays.asList(defaultSnippets);
return this;
return (T) this;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -92,8 +92,9 @@ public class PathParametersSnippet extends AbstractParametersSnippet {
private String extractUrlTemplate(Operation operation) {
String urlTemplate = (String) operation.getAttributes().get(
"org.springframework.restdocs.urlTemplate");
Assert.notNull(urlTemplate,
"urlTemplate not found. Did you use RestDocumentationRequestBuilders to "
Assert.notNull(
urlTemplate,
"urlTemplate not found. If you are using MockMvc, did you use RestDocumentationRequestBuilders to "
+ "build the request?");
return urlTemplate;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -44,9 +44,9 @@ public abstract class MockMvcRestDocumentation {
* @return the configurer
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
*/
public static RestDocumentationMockMvcConfigurer documentationConfiguration(
public static MockMvcRestDocumentationConfigurer documentationConfiguration(
RestDocumentation restDocumentation) {
return new RestDocumentationMockMvcConfigurer(restDocumentation);
return new MockMvcRestDocumentationConfigurer(restDocumentation);
}
/**

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.AbstractConfigurer;
import org.springframework.restdocs.config.RestDocumentationConfigurer;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* A MockMvc-specific {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
*/
public class MockMvcRestDocumentationConfigurer
extends
RestDocumentationConfigurer<MockMvcSnippetConfigurer, MockMvcRestDocumentationConfigurer>
implements MockMvcConfigurer {
private final MockMvcSnippetConfigurer snippetConfigurer = new MockMvcSnippetConfigurer(
this);
private final UriConfigurer uriConfigurer = new UriConfigurer(this);
private final RestDocumentation restDocumentation;
MockMvcRestDocumentationConfigurer(RestDocumentation restDocumentation) {
super();
this.restDocumentation = restDocumentation;
}
/**
* Returns a {@link UriConfigurer} that can be used to configure the request URIs that
* will be documented.
*
* @return the URI configurer
*/
public UriConfigurer uris() {
return this.uriConfigurer;
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return new ConfigurerApplyingRequestPostProcessor(this.restDocumentation,
Arrays.asList(getTemplateEngineConfigurer(),
getWriterResolverConfigurer(), snippets(), this.uriConfigurer));
}
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
// Nothing to do
}
@Override
public MockMvcSnippetConfigurer snippets() {
return this.snippetConfigurer;
}
private static final class ConfigurerApplyingRequestPostProcessor implements
RequestPostProcessor {
private final RestDocumentation restDocumentation;
private final List<AbstractConfigurer> configurers;
private ConfigurerApplyingRequestPostProcessor(
RestDocumentation restDocumentation, List<AbstractConfigurer> configurers) {
this.restDocumentation = restDocumentation;
this.configurers = configurers;
}
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
RestDocumentationContext context = this.restDocumentation.beforeOperation();
request.setAttribute(RestDocumentationContext.class.getName(), context);
Map<String, Object> configuration = new HashMap<>();
configuration.put(MockHttpServletRequest.class.getName(), request);
String urlTemplateAttribute = "org.springframework.restdocs.urlTemplate";
configuration.put(urlTemplateAttribute,
request.getAttribute(urlTemplateAttribute));
request.setAttribute("org.springframework.restdocs.configuration",
configuration);
for (AbstractConfigurer configurer : this.configurers) {
configurer.apply(configuration, context);
}
return request;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,40 +16,33 @@
package org.springframework.restdocs.mockmvc;
import org.springframework.restdocs.config.SnippetConfigurer;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* Base class for {@link NestedConfigurer} implementations.
* A configurer that can be used to configure the generated documentation snippets.
*
* @param <PARENT> The type of the configurer's parent
* @author Andy Wilkinson
*/
abstract class AbstractNestedConfigurer<PARENT extends MockMvcConfigurer> extends
AbstractConfigurer implements NestedConfigurer<PARENT>, MockMvcConfigurer {
public class MockMvcSnippetConfigurer extends
SnippetConfigurer<MockMvcRestDocumentationConfigurer, MockMvcSnippetConfigurer>
implements MockMvcConfigurer {
private final PARENT parent;
protected AbstractNestedConfigurer(PARENT parent) {
this.parent = parent;
}
@Override
public PARENT and() {
return this.parent;
MockMvcSnippetConfigurer(MockMvcRestDocumentationConfigurer parent) {
super(parent);
}
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
this.parent.afterConfigurerAdded(builder);
and().afterConfigurerAdded(builder);
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return this.parent.beforeMockMvcCreated(builder, context);
return and().beforeMockMvcCreated(builder, context);
}
}

View File

@@ -1,198 +0,0 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.mockmvc;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.curl.CurlDocumentation;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.web.context.WebApplicationContext;
/**
* A {@link MockMvcConfigurer} that can be used to configure the documentation.
* <p>
* In the absence of any {@link #snippets() customization} the following snippets will be
* produced by default:
* <ul>
* <li>{@link CurlDocumentation#curlRequest() Curl request}</li>
* <li>{@link HttpDocumentation#httpRequest() HTTP request}</li>
* <li>{@link HttpDocumentation#httpResponse() HTTP response}</li>
* </ul>
* <p>
* In the absence of any {@link #uris() customization}, documented URIs have the following
* defaults:
* <ul>
* <li>Scheme: {@code http}</li>
* <li>Host: {@code localhost}</li>
* <li>Port: {@code 8080}</li>
* </ul>
*
* @author Andy Wilkinson
* @author Dmitriy Mayboroda
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
* @see MockMvcRestDocumentation#documentationConfiguration(RestDocumentation)
*/
public class RestDocumentationMockMvcConfigurer extends MockMvcConfigurerAdapter {
private final UriConfigurer uriConfigurer = new UriConfigurer(this);
private final SnippetConfigurer snippetConfigurer = new SnippetConfigurer(this);
private final RequestPostProcessor requestPostProcessor;
private final TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer();
private final WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer();
/**
* Creates a new {code RestDocumentationMockMvcConfigurer} that will use the given
* {@code restDocumentation} when configuring MockMvc.
*
* @param restDocumentation the rest documentation
* @see MockMvcRestDocumentation#documentationConfiguration(RestDocumentation)
*/
RestDocumentationMockMvcConfigurer(RestDocumentation restDocumentation) {
this.requestPostProcessor = new ConfigurerApplyingRequestPostProcessor(
restDocumentation, this.uriConfigurer, this.writerResolverConfigurer,
this.snippetConfigurer, this.templateEngineConfigurer);
}
/**
* Returns a {@link UriConfigurer} that can be used to configure the request URIs that
* will be documented.
*
* @return the URI configurer
*/
public UriConfigurer uris() {
return this.uriConfigurer;
}
/**
* Returns a {@link SnippetConfigurer} that can be used to configure the snippets that
* will be generated.
*
* @return the snippet configurer
*/
public SnippetConfigurer snippets() {
return this.snippetConfigurer;
}
/**
* Configures the {@link TemplateEngine} that will be used for snippet rendering.
*
* @param templateEngine the template engine to use
* @return {@code this}
*/
public RestDocumentationMockMvcConfigurer templateEngine(TemplateEngine templateEngine) {
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
return this;
}
/**
* Configures the {@link WriterResolver} that will be used to resolve a writer for a
* snippet.
*
* @param writerResolver The writer resolver to use
* @return {@code this}
*/
public RestDocumentationMockMvcConfigurer writerResolver(WriterResolver writerResolver) {
this.writerResolverConfigurer.setWriterResolver(writerResolver);
return this;
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return this.requestPostProcessor;
}
private static final class TemplateEngineConfigurer extends AbstractConfigurer {
private TemplateEngine templateEngine = new MustacheTemplateEngine(
new StandardTemplateResourceResolver());
@Override
void apply(MockHttpServletRequest request) {
request.setAttribute(TemplateEngine.class.getName(), this.templateEngine);
}
void setTemplateEngine(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
}
private static final class WriterResolverConfigurer extends AbstractConfigurer {
private WriterResolver writerResolver;
@Override
void apply(MockHttpServletRequest request) {
WriterResolver resolverToUse = this.writerResolver;
if (resolverToUse == null) {
resolverToUse = new StandardWriterResolver(
new RestDocumentationContextPlaceholderResolver(
(RestDocumentationContext) request
.getAttribute(RestDocumentationContext.class
.getName())));
}
request.setAttribute(WriterResolver.class.getName(), resolverToUse);
}
void setWriterResolver(WriterResolver writerResolver) {
this.writerResolver = writerResolver;
}
}
private static final class ConfigurerApplyingRequestPostProcessor implements
RequestPostProcessor {
private final RestDocumentation restDocumentation;
private final AbstractConfigurer[] configurers;
private ConfigurerApplyingRequestPostProcessor(
RestDocumentation restDocumentation, AbstractConfigurer... configurers) {
this.restDocumentation = restDocumentation;
this.configurers = configurers;
}
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setAttribute(RestDocumentationContext.class.getName(),
this.restDocumentation.beforeOperation());
for (AbstractConfigurer configurer : this.configurers) {
configurer.apply(request);
}
return request;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -22,6 +22,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.SnippetConfigurer;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationResponse;
@@ -33,8 +35,6 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.util.Assert;
import static org.springframework.restdocs.mockmvc.IterableEnumeration.iterable;
/**
* A Spring MVC Test {@code ResultHandler} for documenting RESTful APIs.
*
@@ -85,9 +85,15 @@ public class RestDocumentationResultHandler implements ResultHandler {
@Override
public void handle(MvcResult result) throws Exception {
Map<String, Object> attributes = new HashMap<>();
for (String name : iterable(result.getRequest().getAttributeNames())) {
attributes.put(name, result.getRequest().getAttribute(name));
}
attributes.put(RestDocumentationContext.class.getName(), result.getRequest()
.getAttribute(RestDocumentationContext.class.getName()));
attributes.put("org.springframework.restdocs.urlTemplate", result.getRequest()
.getAttribute("org.springframework.restdocs.urlTemplate"));
@SuppressWarnings("unchecked")
Map<String, Object> configuration = (Map<String, Object>) result.getRequest()
.getAttribute("org.springframework.restdocs.configuration");
attributes.putAll(configuration);
OperationRequest request = this.requestPreprocessor
.preprocess(new MockMvcOperationRequestFactory()
.createOperationRequest(result.getRequest()));
@@ -103,7 +109,7 @@ public class RestDocumentationResultHandler implements ResultHandler {
}
/**
* Adds the given {@code snippets} such that that are documented when this result
* Adds the given {@code snippets} such that they are documented when this result
* handler is called.
*
* @param snippets the snippets to add
@@ -116,9 +122,10 @@ public class RestDocumentationResultHandler implements ResultHandler {
@SuppressWarnings("unchecked")
private List<Snippet> getSnippets(MvcResult result) {
List<Snippet> combinedSnippets = new ArrayList<>((List<Snippet>) result
.getRequest().getAttribute(
"org.springframework.restdocs.mockmvc.defaultSnippets"));
List<Snippet> combinedSnippets = new ArrayList<>(
(List<Snippet>) ((Map<String, Object>) result.getRequest().getAttribute(
"org.springframework.restdocs.configuration"))
.get(SnippetConfigurer.ATTRIBUTE_DEFAULT_SNIPPETS));
combinedSnippets.addAll(this.snippets);
return combinedSnippets;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,7 +16,15 @@
package org.springframework.restdocs.mockmvc;
import java.util.Map;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.AbstractNestedConfigurer;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* A configurer that can be used to configure the documented URIs.
@@ -24,7 +32,8 @@ import org.springframework.mock.web.MockHttpServletRequest;
* @author Andy Wilkinson
*/
public class UriConfigurer extends
AbstractNestedConfigurer<RestDocumentationMockMvcConfigurer> {
AbstractNestedConfigurer<MockMvcRestDocumentationConfigurer> implements
MockMvcConfigurer {
/**
* The default scheme for documented URIs.
@@ -53,7 +62,7 @@ public class UriConfigurer extends
private int port = DEFAULT_PORT;
UriConfigurer(RestDocumentationMockMvcConfigurer parent) {
UriConfigurer(MockMvcRestDocumentationConfigurer parent) {
super(parent);
}
@@ -94,10 +103,23 @@ public class UriConfigurer extends
}
@Override
void apply(MockHttpServletRequest request) {
public void apply(Map<String, Object> configuration, RestDocumentationContext context) {
MockHttpServletRequest request = (MockHttpServletRequest) configuration
.get(MockHttpServletRequest.class.getName());
request.setScheme(this.scheme);
request.setServerPort(this.port);
request.setServerName(this.host);
}
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
and().afterConfigurerAdded(builder);
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return and().beforeMockMvcCreated(builder, context);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -33,12 +33,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RestDocumentationMockMvcConfigurer}.
* Tests for {@link MockMvcRestDocumentationConfigurer}.
*
* @author Andy Wilkinson
* @author Dmitriy Mayboroda
*/
public class RestDocumentationConfigurerTests {
public class MockMvcRestDocumentationConfigurerTests {
private MockHttpServletRequest request = new MockHttpServletRequest();
@@ -47,7 +47,7 @@ public class RestDocumentationConfigurerTests {
@Test
public void defaultConfiguration() {
RequestPostProcessor postProcessor = new RestDocumentationMockMvcConfigurer(
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(
this.restDocumentation).beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -56,7 +56,7 @@ public class RestDocumentationConfigurerTests {
@Test
public void customScheme() {
RequestPostProcessor postProcessor = new RestDocumentationMockMvcConfigurer(
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(
this.restDocumentation).uris().withScheme("https")
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -66,7 +66,7 @@ public class RestDocumentationConfigurerTests {
@Test
public void customHost() {
RequestPostProcessor postProcessor = new RestDocumentationMockMvcConfigurer(
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(
this.restDocumentation).uris().withHost("api.example.com")
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -76,7 +76,7 @@ public class RestDocumentationConfigurerTests {
@Test
public void customPort() {
RequestPostProcessor postProcessor = new RestDocumentationMockMvcConfigurer(
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(
this.restDocumentation).uris().withPort(8081)
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);
@@ -86,7 +86,7 @@ public class RestDocumentationConfigurerTests {
@Test
public void noContentLengthHeaderWhenRequestHasNotContent() {
RequestPostProcessor postProcessor = new RestDocumentationMockMvcConfigurer(
RequestPostProcessor postProcessor = new MockMvcRestDocumentationConfigurer(
this.restDocumentation).uris().withPort(8081)
.beforeMockMvcCreated(null, null);
postProcessor.postProcessRequest(this.request);

View File

@@ -83,7 +83,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for using Spring REST Docs with Spring Test's Mock MVC.
* Integration tests for using Spring REST Docs with Spring Test's MockMvc.
*
* @author Andy Wilkinson
* @author Dewet Diener
@@ -112,8 +112,10 @@ public class MockMvcRestDocumentationIntegrationTests {
@Test
public void basicSnippetGeneration() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)).build();
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(this.context)
.apply(new MockMvcRestDocumentationConfigurer(this.restDocumentation)
.snippets().withEncoding("UTF-8")).build();
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document("basic"));

View File

@@ -0,0 +1,16 @@
dependencies {
compile project(':spring-restdocs-core')
compile 'com.jayway.restassured:rest-assured'
testCompile 'org.mockito:mockito-core'
testCompile 'org.hamcrest:hamcrest-library'
testCompile 'org.springframework.hateoas:spring-hateoas'
testCompile 'org.springframework.boot:spring-boot-starter-web:1.2.5.RELEASE'
testCompile 'org.springframework:spring-test'
testCompile project(path: ':spring-restdocs-core', configuration: 'testArtifacts')
testRuntime 'commons-logging:commons-logging:1.2'
}
test {
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.restdocs.*"
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
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 com.jayway.restassured.response.Header;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.MultiPartSpecification;
/**
* A factory for creating an {@link OperationRequest} derived from a REST Assured
* {@link FilterableRequestSpecification}.
*
* @author Andy Wilkinson
*/
class RestAssuredOperationRequestFactory {
OperationRequest createOperationRequest(FilterableRequestSpecification requestSpec) {
return new OperationRequestFactory().create(URI.create(requestSpec.getURI()),
HttpMethod.valueOf(requestSpec.getMethod().name()),
extractContent(requestSpec), extractHeaders(requestSpec),
extractParameters(requestSpec), extractParts(requestSpec));
}
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 == null) {
return new byte[0];
}
else {
throw new IllegalStateException("Unsupported request content: "
+ content.getClass().getName());
}
}
private HttpHeaders extractHeaders(FilterableRequestSpecification requestSpec) {
HttpHeaders httpHeaders = new HttpHeaders();
for (Header header : requestSpec.getHeaders()) {
httpHeaders.add(header.getName(), header.getValue());
}
return httpHeaders;
}
private Parameters extractParameters(FilterableRequestSpecification requestSpec) {
Parameters parameters = new Parameters();
for (Entry<String, ?> entry : requestSpec.getQueryParams().entrySet()) {
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.TEXT_PLAIN
: MediaType.parseMediaType(multiPartSpec.getMimeType()));
parts.add(new OperationRequestPartFactory().create(
multiPartSpec.getControlName(), multiPartSpec.getFileName(),
convertContent(multiPartSpec.getContent()), headers));
}
return parts;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import com.jayway.restassured.response.Header;
import com.jayway.restassured.response.Response;
/**
* A factory for creating an {@link OperationResponse} derived from a REST Assured
* {@link Response}.
*
* @author Andy Wilkinson
*/
class RestAssuredOperationResponseFactory {
OperationResponse createOperationResponse(Response response) {
return new OperationResponseFactory().create(
HttpStatus.valueOf(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,108 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import org.springframework.restdocs.RestDocumentation;
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
*/
public abstract class RestAssuredRestDocumentation {
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(identifier, 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(identifier, 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(identifier, 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(identifier, requestPreprocessor,
responsePreprocessor, snippets);
}
/**
* Provides access to a {@link RestAssuredRestDocumentationConfigurer} that can be
* used to configure Spring REST Docs using the given {@code restDocumentation}.
*
* @param restDocumentation the REST documentation
* @return the configurer
*/
public static RestAssuredRestDocumentationConfigurer documentationConfiguration(
RestDocumentation restDocumentation) {
return new RestAssuredRestDocumentationConfigurer(restDocumentation);
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.AbstractConfigurer;
import org.springframework.restdocs.config.RestDocumentationConfigurer;
import com.jayway.restassured.filter.Filter;
import com.jayway.restassured.filter.FilterContext;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.FilterableResponseSpecification;
/**
* A REST Assured-specific {@link RestDocumentationConfigurer}.
*
* @author Andy Wilkinson
*/
public final class RestAssuredRestDocumentationConfigurer
extends
RestDocumentationConfigurer<RestAssuredSnippetConfigurer, RestAssuredRestDocumentationConfigurer>
implements Filter {
private final RestAssuredSnippetConfigurer snippetConfigurer = new RestAssuredSnippetConfigurer(
this);
private final List<AbstractConfigurer> configurers;
private final RestDocumentation restDocumentation;
RestAssuredRestDocumentationConfigurer(RestDocumentation restDocumentation) {
this.restDocumentation = restDocumentation;
this.configurers = Arrays.asList(getTemplateEngineConfigurer(),
getWriterResolverConfigurer(), snippets());
}
@Override
public RestAssuredSnippetConfigurer snippets() {
return this.snippetConfigurer;
}
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext filterContext) {
RestDocumentationContext context = this.restDocumentation.beforeOperation();
filterContext.setValue(RestDocumentationContext.class.getName(), context);
Map<String, Object> configuration = new HashMap<>();
filterContext.setValue("org.springframework.restdocs.configuration",
configuration);
for (AbstractConfigurer configurer : this.configurers) {
configurer.apply(configuration, context);
}
return filterContext.next(requestSpec, responseSpec);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import org.springframework.restdocs.config.SnippetConfigurer;
import com.jayway.restassured.filter.Filter;
import com.jayway.restassured.filter.FilterContext;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.FilterableResponseSpecification;
/**
* A configurer that can be used to configure the generated documentation snippets when
* using REST Assured.
*
* @author Andy Wilkinson
*/
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,164 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.config.SnippetConfigurer;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.StandardOperation;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.snippet.Snippet;
import com.jayway.restassured.filter.Filter;
import com.jayway.restassured.filter.FilterContext;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.FilterableResponseSpecification;
/**
* A REST Assured {@link Filter} for documenting RESTful APIs.
*
* @author Andy Wilkinson
*/
public final class RestDocumentationFilter implements Filter {
private final String identifier;
private final OperationRequestPreprocessor requestPreprocessor;
private final OperationResponsePreprocessor responsePreprocessor;
private final List<Snippet> snippets;
RestDocumentationFilter(String identifier, Snippet... snippets) {
this(identifier, new IdentityOperationRequestPreprocessor(),
new IdentityOperationResponsePreprocessor(), snippets);
}
RestDocumentationFilter(String identifier,
OperationRequestPreprocessor operationRequestPreprocessor,
Snippet... snippets) {
this(identifier, operationRequestPreprocessor,
new IdentityOperationResponsePreprocessor(), snippets);
}
RestDocumentationFilter(String identifier,
OperationResponsePreprocessor operationResponsePreprocessor,
Snippet... snippets) {
this(identifier, new IdentityOperationRequestPreprocessor(),
operationResponsePreprocessor, snippets);
}
RestDocumentationFilter(String identifier,
OperationRequestPreprocessor requestPreprocessor,
OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) {
this.identifier = identifier;
this.requestPreprocessor = requestPreprocessor;
this.responsePreprocessor = responsePreprocessor;
this.snippets = new ArrayList<>(Arrays.asList(snippets));
}
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext context) {
Response response = context.next(requestSpec, responseSpec);
OperationRequest operationRequest = this.requestPreprocessor
.preprocess(new RestAssuredOperationRequestFactory()
.createOperationRequest(requestSpec));
OperationResponse operationResponse = this.responsePreprocessor
.preprocess(new RestAssuredOperationResponseFactory()
.createOperationResponse(response));
RestDocumentationContext documentationContext = context
.getValue(RestDocumentationContext.class.getName());
Map<String, Object> attributes = new HashMap<>();
attributes.put(RestDocumentationContext.class.getName(), documentationContext);
attributes.put("org.springframework.restdocs.urlTemplate",
requestSpec.getUserDefinedPath());
Map<String, Object> configuration = context
.getValue("org.springframework.restdocs.configuration");
attributes.putAll(configuration);
Operation operation = new StandardOperation(this.identifier, operationRequest,
operationResponse, attributes);
try {
for (Snippet snippet : getSnippets(configuration)) {
snippet.document(operation);
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
return response;
}
/**
* Adds the given {@code snippets} such that they are documented when this result
* handler is called.
*
* @param snippets the snippets to add
* @return this {@code RestDocumentationFilter}
*/
public RestDocumentationFilter snippets(Snippet... snippets) {
this.snippets.addAll(Arrays.asList(snippets));
return this;
}
@SuppressWarnings("unchecked")
private List<Snippet> getSnippets(Map<String, Object> configuration) {
List<Snippet> combinedSnippets = new ArrayList<>(
(List<Snippet>) configuration
.get(SnippetConfigurer.ATTRIBUTE_DEFAULT_SNIPPETS));
combinedSnippets.addAll(this.snippets);
return combinedSnippets;
}
private static final class IdentityOperationRequestPreprocessor implements
OperationRequestPreprocessor {
@Override
public OperationRequest preprocess(OperationRequest request) {
return request;
}
}
private static final class IdentityOperationResponsePreprocessor implements
OperationResponsePreprocessor {
@Override
public OperationResponse preprocess(OperationResponse response) {
return response;
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured.operation.preprocess;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationResponse;
/**
* Static factory methods for creating
* {@link org.springframework.restdocs.operation.preprocess.OperationPreprocessor
* OperationPreprocessors} for use with REST Assured. They can be applied to an
* {@link Operation Operation's} {@link OperationRequest request} or
* {@link OperationResponse response} before it is documented.
*
* @author Andy Wilkinson
*/
public abstract class RestAssuredPreprocessors {
private RestAssuredPreprocessors() {
}
/**
* Returns a {@code UriModifyingOperationPreprocessor} that will modify URIs in the
* request or response by changing one or more of their host, scheme, and port.
*
* @return the preprocessor
*/
public static UriModifyingOperationPreprocessor modifyUris() {
return new UriModifyingOperationPreprocessor();
}
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured.operation.preprocess;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.http.HttpHeaders;
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.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.preprocess.ContentModifier;
import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
* An {@link OperationPreprocessor} that modifies URIs in the request and in the response
* by changing one or more of their host, scheme, and port. URIs in the following
* locations are modified:
* <ul>
* <li>{@link OperationRequest#getUri() Request URI}
* <li>{@link OperationRequest#getHeaders() Request headers}
* <li>{@link OperationRequest#getContent() Request content}
* <li>{@link OperationRequestPart#getHeaders() Request part headers}
* <li>{@link OperationRequestPart#getContent() Request part content}
* <li>{@link OperationResponse#getHeaders() Response headers}
* <li>{@link OperationResponse#getContent() Response content}
* </ul>
*
* @author Andy Wilkinson
*/
public final class UriModifyingOperationPreprocessor implements OperationPreprocessor {
private final UriModifyingContentModifier contentModifier = new UriModifyingContentModifier();
private final OperationPreprocessor contentModifyingDelegate = new ContentModifyingOperationPreprocessor(
this.contentModifier);
private String scheme;
private String host;
private String port;
/**
* Modifies the URI to use the given {@code scheme}. {@code null}, the default, will
* leave the scheme unchanged.
*
* @param scheme the scheme
* @return {@code this}
*/
public UriModifyingOperationPreprocessor scheme(String scheme) {
this.scheme = scheme;
this.contentModifier.setScheme(scheme);
return this;
}
/**
* Modifies the URI to use the given {@code host}. {@code null}, the default, will
* leave the host unchanged.
*
* @param host the host
* @return {@code this}
*/
public UriModifyingOperationPreprocessor host(String host) {
this.host = host;
this.contentModifier.setHost(host);
return this;
}
/**
* Modifies the URI to use the given {@code port}.
*
* @param port the port
* @return {@code this}
*/
public UriModifyingOperationPreprocessor port(int port) {
return port(Integer.toString(port));
}
/**
* Removes the port from the URI.
*
* @return {@code this}
*/
public UriModifyingOperationPreprocessor removePort() {
return port("");
}
private UriModifyingOperationPreprocessor port(String port) {
this.port = port;
this.contentModifier.setPort(port);
return this;
}
@Override
public OperationRequest preprocess(OperationRequest request) {
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(request.getUri());
if (this.scheme != null) {
uriBuilder.scheme(this.scheme);
}
if (this.host != null) {
uriBuilder.host(this.host);
}
if (this.port != null) {
if (StringUtils.hasText(this.port)) {
uriBuilder.port(this.port);
}
else {
uriBuilder.port(null);
}
}
HttpHeaders modifiedHeaders = modify(request.getHeaders());
if (this.host != null) {
modifiedHeaders.set(HttpHeaders.HOST, this.host);
}
return this.contentModifyingDelegate.preprocess(new OperationRequestFactory()
.create(uriBuilder.build().toUri(), request.getMethod(),
request.getContent(), modifiedHeaders, request.getParameters(),
modify(request.getParts())));
}
@Override
public OperationResponse preprocess(OperationResponse response) {
return this.contentModifyingDelegate.preprocess(new OperationResponseFactory()
.create(response.getStatus(), modify(response.getHeaders()),
response.getContent()));
}
private HttpHeaders modify(HttpHeaders headers) {
HttpHeaders modified = new HttpHeaders();
for (Entry<String, List<String>> header : headers.entrySet()) {
for (String value : header.getValue()) {
modified.add(header.getKey(), this.contentModifier.modify(value));
}
}
return modified;
}
private Collection<OperationRequestPart> modify(Collection<OperationRequestPart> parts) {
List<OperationRequestPart> modifiedParts = new ArrayList<>();
OperationRequestPartFactory factory = new OperationRequestPartFactory();
for (OperationRequestPart part : parts) {
modifiedParts.add(factory.create(part.getName(), part.getSubmittedFileName(),
this.contentModifier.modifyContent(part.getContent(), part
.getHeaders().getContentType()), modify(part.getHeaders())));
}
return modifiedParts;
}
private static final class UriModifyingContentModifier implements ContentModifier {
private static final Pattern SCHEME_HOST_PORT_PATTERN = Pattern
.compile("(http[s]?)://([^/:#?]+)(:[0-9]+)?");
private String scheme;
private String host;
private String port;
private void setScheme(String scheme) {
this.scheme = scheme;
}
private void setHost(String host) {
this.host = host;
}
private void setPort(String port) {
this.port = port;
}
@Override
public byte[] modifyContent(byte[] content, MediaType contentType) {
String input;
if (contentType != null && contentType.getCharSet() != null) {
input = new String(content, contentType.getCharSet());
}
else {
input = new String(content);
}
return modify(input).getBytes();
}
private String modify(String input) {
List<String> replacements = Arrays.asList(this.scheme, this.host,
StringUtils.hasText(this.port) ? ":" + this.port : this.port);
int previous = 0;
Matcher matcher = SCHEME_HOST_PORT_PATTERN.matcher(input);
StringBuilder builder = new StringBuilder();
while (matcher.find()) {
for (int i = 1; i <= matcher.groupCount(); i++) {
if (matcher.start(i) >= 0) {
builder.append(input.substring(previous, matcher.start(i)));
}
if (matcher.start(i) >= 0) {
previous = matcher.end(i);
}
builder.append(getReplacement(matcher.group(i),
replacements.get(i - 1)));
}
}
if (previous < input.length()) {
builder.append(input.substring(previous));
}
return builder.toString();
}
private String getReplacement(String original, String candidate) {
if (candidate != null) {
return candidate;
}
if (original != null) {
return original;
}
return "";
}
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* REST Assured-specific support for preprocessing an operation prior to it being
* documented.
*/
package org.springframework.restdocs.restassured.operation.preprocess;

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Core classes for using Spring REST Docs with REST Assured.
*/
package org.springframework.restdocs.restassured;

View File

@@ -0,0 +1,251 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
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.restassured.RestAssuredOperationRequestFactoryTests.TestApplication;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RestAssuredOperationRequestFactory}.
*
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
public class RestAssuredOperationRequestFactoryTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final RestAssuredOperationRequestFactory factory = new RestAssuredOperationRequestFactory();
@Value("${local.server.port}")
private int port;
@Test
public void requestUri() {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.get("/foo/bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getUri(),
is(equalTo(URI.create("http://localhost:" + this.port + "/foo/bar"))));
}
@Test
public void requestMethod() {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.head("/foo/bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getMethod(), is(equalTo(HttpMethod.HEAD)));
}
@Test
public void queryStringParameters() {
RequestSpecification requestSpec = RestAssured.given().port(this.port)
.queryParam("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@Test
public void queryStringFromUrlParameters() {
RequestSpecification requestSpec = RestAssured.given().port(this.port);
requestSpec.get("/?foo=bar");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@Test
public void formParameters() {
RequestSpecification requestSpec = RestAssured.given().port(this.port)
.formParameter("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@Test
public void requestParameters() {
RequestSpecification requestSpec = RestAssured.given().port(this.port)
.parameter("foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getParameters().size(), is(1));
assertThat(request.getParameters().get("foo"), is(equalTo(Arrays.asList("bar"))));
}
@Test
public void headers() {
RequestSpecification requestSpec = RestAssured.given().port(this.port)
.header("Foo", "bar");
requestSpec.get("/");
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getHeaders().toString(), request.getHeaders().size(), is(3));
assertThat(request.getHeaders().get("Foo"), is(equalTo(Arrays.asList("bar"))));
assertThat(request.getHeaders().get("Accept"), is(equalTo(Arrays.asList("*/*"))));
assertThat(request.getHeaders().get("Host"),
is(equalTo(Arrays.asList("localhost"))));
}
@Test
public void multipart() {
RequestSpecification requestSpec = RestAssured.given().port(this.port)
.multiPart("a", "a.txt", "alpha", null)
.multiPart("b", new ObjectBody("bar"), "application/json");
requestSpec.post().then().statusCode(200);
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
Collection<OperationRequestPart> parts = request.getParts();
assertThat(parts.size(), is(2));
Iterator<OperationRequestPart> iterator = parts.iterator();
OperationRequestPart part = iterator.next();
assertThat(part.getName(), is(equalTo("a")));
assertThat(part.getSubmittedFileName(), is(equalTo("a.txt")));
assertThat(part.getContentAsString(), is(equalTo("alpha")));
assertThat(part.getHeaders().getContentType(), is(equalTo(MediaType.TEXT_PLAIN)));
part = iterator.next();
assertThat(part.getName(), is(equalTo("b")));
assertThat(part.getSubmittedFileName(), is(equalTo("file")));
assertThat(part.getContentAsString(), is(equalTo("{\"foo\":\"bar\"}")));
assertThat(part.getHeaders().getContentType(),
is(equalTo(MediaType.APPLICATION_JSON)));
}
@Test
public void byteArrayBody() {
RequestSpecification requestSpec = RestAssured.given().body("body".getBytes())
.port(this.port);
requestSpec.post();
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
}
@Test
public void stringBody() {
RequestSpecification requestSpec = RestAssured.given().body("body")
.port(this.port);
requestSpec.post();
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString(), is(equalTo("body")));
}
@Test
public void objectBody() {
RequestSpecification requestSpec = RestAssured.given()
.body(new ObjectBody("bar")).port(this.port);
requestSpec.post();
OperationRequest request = this.factory
.createOperationRequest((FilterableRequestSpecification) requestSpec);
assertThat(request.getContentAsString(), is(equalTo("{\"foo\":\"bar\"}")));
}
@Test
public void inputStreamBodyIsNotSupported() {
RequestSpecification requestSpec = RestAssured.given()
.body(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 }))
.port(this.port);
requestSpec.post();
this.thrown
.expectMessage(equalTo("Unsupported request content: java.io.ByteArrayInputStream"));
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
}
@Test
public void fileBodyIsNotSupported() {
RequestSpecification requestSpec = RestAssured.given()
.body(new File("src/test/resources/body.txt")).port(this.port);
requestSpec.post();
this.thrown.expectMessage(equalTo("Unsupported request content: java.io.File"));
this.factory.createOperationRequest((FilterableRequestSpecification) requestSpec);
}
/**
* Minimal test web application.
*/
@Configuration
@EnableAutoConfiguration
@RestController
static class TestApplication {
@RequestMapping("/")
void handle() {
}
}
/**
* Sample object body to verify JSON serialization.
*/
static class ObjectBody {
private final String foo;
ObjectBody(String foo) {
this.foo = foo;
}
public String getFoo() {
return this.foo;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import com.jayway.restassured.filter.FilterContext;
import com.jayway.restassured.specification.FilterableRequestSpecification;
import com.jayway.restassured.specification.FilterableResponseSpecification;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.hasEntry;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link RestAssuredRestDocumentationConfigurer}.
*
* @author Andy Wilkinson
*/
public class RestAssuredRestDocumentationConfigurerTests {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("build");
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).setValue(
eq("org.springframework.restdocs.configuration"), any(Map.class));
}
@Test
public void configurationIsAddedToTheContext() {
this.configurer.filter(this.requestSpec, this.responseSpec, this.filterContext);
@SuppressWarnings("rawtypes")
ArgumentCaptor<Map> configurationCaptor = ArgumentCaptor.forClass(Map.class);
verify(this.filterContext).setValue(
eq("org.springframework.restdocs.configuration"),
configurationCaptor.capture());
@SuppressWarnings("unchecked")
Map<String, Object> configuration = configurationCaptor.getValue();
assertThat(
configuration,
hasEntry(equalTo(TemplateEngine.class.getName()),
instanceOf(TemplateEngine.class)));
assertThat(
configuration,
hasEntry(equalTo(WriterResolver.class.getName()),
instanceOf(WriterResolver.class)));
assertThat(
configuration,
hasEntry(equalTo("org.springframework.restdocs.defaultSnippets"),
instanceOf(List.class)));
}
}

View File

@@ -0,0 +1,374 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.restdocs.hypermedia.Link;
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationIntegrationTests.TestApplication;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
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.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.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.modifyUris;
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
import static org.springframework.restdocs.test.SnippetMatchers.snippet;
/**
* Integration tests for using Spring REST Docs with REST Assured.
*
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
public class RestAssuredRestDocumentationIntegrationTests {
@Rule
public RestDocumentation restDocumentation = new RestDocumentation(
"build/generated-snippets");
@Value("${local.server.port}")
private int port;
@Test
public void defaultSnippetGeneration() {
given().port(this.port)
.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(this.port)
.filter(documentationConfiguration(this.restDocumentation))
.filter(document("curl-snippet-with-content")).accept("application/json")
.content("content").contentType(contentType).post("/").then()
.statusCode(200);
assertThat(
new File(
"build/generated-snippets/curl-snippet-with-content/curl-request.adoc"),
is(snippet().withContents(
codeBlock("bash").content(
"$ curl 'http://localhost:" + this.port + "/' -i "
+ "-X POST -H 'Accept: application/json' "
+ "-H 'Content-Type: " + contentType + "' "
+ "-d 'content'"))));
}
@Test
public void curlSnippetWithQueryStringOnPost() throws Exception {
given().port(this.port)
.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"),
is(snippet().withContents(
codeBlock("bash").content(
"$ curl " + "'http://localhost:" + this.port
+ "/?foo=bar' -i -X POST "
+ "-H 'Accept: application/json' "
+ "-H 'Content-Type: " + contentType + "' "
+ "-d 'a=alpha'"))));
}
@Test
public void linksSnippet() throws Exception {
given().port(this.port)
.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(this.port)
.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(this.port)
.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(this.port)
.filter(documentationConfiguration(this.restDocumentation))
.filter(document("request-fields", requestFields(fieldWithPath("a")
.description("The description")))).accept("application/json")
.content("{\"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 responseFieldsSnippet() throws Exception {
given().port(this.port)
.filter(documentationConfiguration(this.restDocumentation))
.filter(document(
"response-fields",
responseFields(
fieldWithPath("a").description("The description"),
fieldWithPath("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(this.port)
.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(this.port)
.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 preprocessedRequest() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(this.port)
.filter(documentationConfiguration(this.restDocumentation))
.header("a", "alpha")
.header("b", "bravo")
.contentType("application/json")
.accept("application/json")
.content("{\"a\":\"alpha\"}")
.filter(document("original-request"))
.filter(document(
"preprocessed-request",
preprocessRequest(
prettyPrint(),
removeHeaders("a", HttpHeaders.HOST,
HttpHeaders.CONTENT_LENGTH),
replacePattern(pattern, "\"<<beta>>\"")))).get("/")
.then().statusCode(200);
assertThat(
new File("build/generated-snippets/original-request/http-request.adoc"),
is(snippet()
.withContents(
httpRequest(RequestMethod.GET, "/")
.header("a", "alpha")
.header("b", "bravo")
.header("Accept",
MediaType.APPLICATION_JSON_VALUE)
.header("Content-Type",
"application/json; charset=UTF-8")
.header("Host", "localhost")
.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"),
is(snippet()
.withContents(
httpRequest(RequestMethod.GET, "/")
.header("b", "bravo")
.header("Accept",
MediaType.APPLICATION_JSON_VALUE)
.header("Content-Type",
"application/json; charset=UTF-8")
.content(prettyPrinted))));
}
@Test
public void preprocessedResponse() throws Exception {
Pattern pattern = Pattern.compile("(\"alpha\")");
given().port(this.port)
.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"),
is(snippet().withContents(
httpResponse(HttpStatus.OK)
.header("Foo", "https://api.example.com/foo/bar")
.header("Content-Type", "application/json;charset=UTF-8")
.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(this.port).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"),
is(snippet().withContents(equalTo("Custom curl request"))));
}
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
for (String snippet : snippets) {
File snippetFile = new File(directory, snippet);
assertTrue("Snippet " + snippetFile + " not found", snippetFile.isFile());
}
}
/**
* Minimal test application called by the tests.
*/
@Configuration
@EnableAutoConfiguration
@RestController
static class TestApplication {
@RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> foo() {
Map<String, Object> response = new HashMap<>();
response.put("a", "alpha");
response.put("links", Arrays.asList(new Link("rel", "href")));
HttpHeaders headers = new HttpHeaders();
headers.add("a", "alpha");
headers.add("Foo", "http://localhost:12345/foo/bar");
return new ResponseEntity<>(response, headers, HttpStatus.OK);
}
@RequestMapping(value = "/company/5", produces = MediaType.APPLICATION_JSON_VALUE)
public String bar() {
return "{\"companyName\": \"FooBar\",\"employee\": [{\"name\": \"Lorem\",\"age\": \"42\"},{\"name\": \"Ipsum\",\"age\": \"24\"}]}";
}
}
}

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.restassured.operation.preprocess;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
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.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link UriModifyingOperationPreprocessor}.
*
* @author Andy Wilkinson
*/
public class UriModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory();
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
private final UriModifyingOperationPreprocessor preprocessor = new UriModifyingOperationPreprocessor();
@Test
public void requestUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://localhost:12345"));
assertThat(processed.getUri(), is(equalTo(URI.create("https://localhost:12345"))));
}
@Test
public void requestUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345"));
assertThat(processed.getUri(),
is(equalTo(URI.create("http://api.example.com:12345"))));
}
@Test
public void requestUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345"));
assertThat(processed.getUri(),
is(equalTo(URI.create("http://api.example.com:23456"))));
}
@Test
public void requestUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345"));
assertThat(processed.getUri(), is(equalTo(URI.create("http://api.example.com"))));
}
@Test
public void requestUriPathIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345/foo/bar"));
assertThat(processed.getUri(),
is(equalTo(URI.create("http://api.example.com/foo/bar"))));
}
@Test
public void requestUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345?foo=bar"));
assertThat(processed.getUri(),
is(equalTo(URI.create("http://api.example.com?foo=bar"))));
}
@Test
public void requestUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithUri("http://api.example.com:12345#foo"));
assertThat(processed.getUri(),
is(equalTo(URI.create("http://api.example.com#foo"))));
}
@Test
public void requestContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'https://localhost:12345' should be used")));
}
@Test
public void requestContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://api.example.com:12345' should be used")));
}
@Test
public void requestContentUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost:23456' should be used")));
}
@Test
public void requestContentUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost' should be used")));
}
@Test
public void multipleRequestContentUrisCanBeModified() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
assertThat(
new String(processed.getContent()),
is(equalTo("Use 'http://localhost' or 'https://localhost' to access the service")));
}
@Test
public void requestContentUriPathIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost/foo/bar' should be used")));
}
@Test
public void requestContentUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost?foo=bar' should be used")));
}
@Test
public void requestContentUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationRequest processed = this.preprocessor
.preprocess(createRequestWithContent("The uri 'http://localhost:12345#foo' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost#foo' should be used")));
}
@Test
public void responseContentUriSchemeCanBeModified() {
this.preprocessor.scheme("https");
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'https://localhost:12345' should be used")));
}
@Test
public void responseContentUriHostCanBeModified() {
this.preprocessor.host("api.example.com");
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://api.example.com:12345' should be used")));
}
@Test
public void responseContentUriPortCanBeModified() {
this.preprocessor.port(23456);
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost:23456' should be used")));
}
@Test
public void responseContentUriPortCanBeRemoved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost' should be used")));
}
@Test
public void multipleResponseContentUrisCanBeModified() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("Use 'http://localhost:12345' or 'https://localhost:23456' to access the service"));
assertThat(
new String(processed.getContent()),
is(equalTo("Use 'http://localhost' or 'https://localhost' to access the service")));
}
@Test
public void responseContentUriPathIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345/foo/bar' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost/foo/bar' should be used")));
}
@Test
public void responseContentUriQueryIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345?foo=bar' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost?foo=bar' should be used")));
}
@Test
public void responseContentUriAnchorIsPreserved() {
this.preprocessor.removePort();
OperationResponse processed = this.preprocessor
.preprocess(createResponseWithContent("The uri 'http://localhost:12345#foo' should be used"));
assertThat(new String(processed.getContent()),
is(equalTo("The uri 'http://localhost#foo' should be used")));
}
@Test
public void urisInRequestHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(createRequestWithHeader("Foo", "http://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo"),
is(equalTo("http://api.example.com:12345")));
assertThat(processed.getHeaders().getFirst("Host"),
is(equalTo("api.example.com")));
}
@Test
public void urisInResponseHeadersCanBeModified() {
OperationResponse processed = this.preprocessor.host("api.example.com")
.preprocess(createResponseWithHeader("Foo", "http://locahost:12345"));
assertThat(processed.getHeaders().getFirst("Foo"),
is(equalTo("http://api.example.com:12345")));
}
@Test
public void urisInRequestPartHeadersCanBeModified() {
OperationRequest processed = this.preprocessor.host("api.example.com")
.preprocess(
createRequestWithPartWithHeader("Foo", "http://locahost:12345"));
assertThat(processed.getParts().iterator().next().getHeaders().getFirst("Foo"),
is(equalTo("http://api.example.com:12345")));
}
@Test
public void urisInRequestPartContentCanBeModified() {
OperationRequest processed = this.preprocessor
.host("api.example.com")
.preprocess(
createRequestWithPartWithContent("The uri 'http://localhost:12345' should be used"));
assertThat(new String(processed.getParts().iterator().next().getContent()),
is(equalTo("The uri 'http://api.example.com:12345' should be used")));
}
private OperationRequest createRequestWithUri(String uri) {
return this.requestFactory.create(URI.create(uri), HttpMethod.GET, new byte[0],
new HttpHeaders(), new Parameters(),
Collections.<OperationRequestPart>emptyList());
}
private OperationRequest createRequestWithContent(String content) {
return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
content.getBytes(), new HttpHeaders(), new Parameters(),
Collections.<OperationRequestPart>emptyList());
}
private OperationRequest createRequestWithHeader(String name, String value) {
HttpHeaders headers = new HttpHeaders();
headers.add(name, value);
return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
new byte[0], headers, new Parameters(),
Collections.<OperationRequestPart>emptyList());
}
private OperationRequest createRequestWithPartWithHeader(String name, String value) {
HttpHeaders headers = new HttpHeaders();
headers.add(name, value);
return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
new byte[0], new HttpHeaders(), new Parameters(), Arrays
.asList(new OperationRequestPartFactory().create("part",
"fileName", new byte[0], headers)));
}
private OperationRequest createRequestWithPartWithContent(String content) {
return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET,
new byte[0], new HttpHeaders(), new Parameters(), Arrays
.asList(new OperationRequestPartFactory().create("part",
"fileName", content.getBytes(), new HttpHeaders())));
}
private OperationResponse createResponseWithContent(String content) {
return this.responseFactory.create(HttpStatus.OK, new HttpHeaders(),
content.getBytes());
}
private OperationResponse createResponseWithHeader(String name, String value) {
HttpHeaders headers = new HttpHeaders();
headers.add(name, value);
return this.responseFactory.create(HttpStatus.OK, headers, new byte[0]);
}
}

View File

@@ -0,0 +1 @@
file