diff --git a/build.gradle b/build.gradle index 30c5a2b0..a286bdee 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index b7908d74..ebc91d6f 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -73,7 +73,7 @@ + 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.*" /> diff --git a/docs/build.gradle b/docs/build.gradle index 5241018c..2816cd28 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -4,6 +4,7 @@ plugins { dependencies { testCompile project(':spring-restdocs-mockmvc') + testCompile project(':spring-restdocs-restassured') testCompile 'javax.validation:validation-api' } diff --git a/docs/src/docs/asciidoc/configuration.adoc b/docs/src/docs/asciidoc/configuration.adoc index ae1b601f..23958800 100644 --- a/docs/src/docs/asciidoc/configuration.adoc +++ b/docs/src/docs/asciidoc/configuration.adoc @@ -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 +<> 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] ---- \ No newline at end of file diff --git a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc index 2bc11527..90010638 100644 --- a/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc +++ b/docs/src/docs/asciidoc/customizing-requests-and-responses.adoc @@ -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 <>: -[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 <> 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 <>. + +`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 diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 25fc52b1..c68b9d43 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -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 <>, 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 <> 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 diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc index 8f8a11ca..2dc9bc92 100644 --- a/docs/src/docs/asciidoc/getting-started.adoc +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -83,7 +83,9 @@ the configuration are described below. ---- -<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 -<> 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 <> 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: * `/index/curl-request.adoc` diff --git a/docs/src/docs/asciidoc/introduction.adoc b/docs/src/docs/asciidoc/introduction.adoc index 9590ac35..3bfaef7d 100644 --- a/docs/src/docs/asciidoc/introduction.adoc +++ b/docs/src/docs/asciidoc/introduction.adoc @@ -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 diff --git a/docs/src/test/java/com/example/Constraints.java b/docs/src/test/java/com/example/Constraints.java index 5bcea859..254a9577 100644 --- a/docs/src/test/java/com/example/Constraints.java +++ b/docs/src/test/java/com/example/Constraints.java @@ -41,6 +41,7 @@ public class Constraints { @NotNull @Size(min = 8) String password; + } // end::constraints[] diff --git a/docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java similarity index 86% rename from docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java rename to docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java index a964c964..fcd6a742 100644 --- a/docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java +++ b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java @@ -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[] } diff --git a/docs/src/test/java/com/example/CustomEncoding.java b/docs/src/test/java/com/example/mockmvc/CustomEncoding.java similarity index 87% rename from docs/src/test/java/com/example/CustomEncoding.java rename to docs/src/test/java/com/example/mockmvc/CustomEncoding.java index 4490e8a4..80130ef1 100644 --- a/docs/src/test/java/com/example/CustomEncoding.java +++ b/docs/src/test/java/com/example/mockmvc/CustomEncoding.java @@ -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[] } diff --git a/docs/src/test/java/com/example/CustomUriConfiguration.java b/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java similarity index 94% rename from docs/src/test/java/com/example/CustomUriConfiguration.java rename to docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java index b70d4e08..9fe6130c 100644 --- a/docs/src/test/java/com/example/CustomUriConfiguration.java +++ b/docs/src/test/java/com/example/mockmvc/CustomUriConfiguration.java @@ -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"); diff --git a/docs/src/test/java/com/example/EveryTestPreprocessing.java b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java similarity index 84% rename from docs/src/test/java/com/example/EveryTestPreprocessing.java rename to docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java index b189f8ff..e1f9b996 100644 --- a/docs/src/test/java/com/example/EveryTestPreprocessing.java +++ b/docs/src/test/java/com/example/mockmvc/EveryTestPreprocessing.java @@ -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(); } diff --git a/docs/src/test/java/com/example/ExampleApplicationTests.java b/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java similarity index 85% rename from docs/src/test/java/com/example/ExampleApplicationTests.java rename to docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java index 70fb8e18..97abfa05 100644 --- a/docs/src/test/java/com/example/ExampleApplicationTests.java +++ b/docs/src/test/java/com/example/mockmvc/ExampleApplicationTests.java @@ -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[] } diff --git a/docs/src/test/java/com/example/HttpHeaders.java b/docs/src/test/java/com/example/mockmvc/HttpHeaders.java similarity index 63% rename from docs/src/test/java/com/example/HttpHeaders.java rename to docs/src/test/java/com/example/mockmvc/HttpHeaders.java index f47e3971..e31ddbf8 100644 --- a/docs/src/test/java/com/example/HttpHeaders.java +++ b/docs/src/test/java/com/example/mockmvc/HttpHeaders.java @@ -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[] } } diff --git a/docs/src/test/java/com/example/Hypermedia.java b/docs/src/test/java/com/example/mockmvc/Hypermedia.java similarity index 96% rename from docs/src/test/java/com/example/Hypermedia.java rename to docs/src/test/java/com/example/mockmvc/Hypermedia.java index 3cf6c1bc..6358fa95 100644 --- a/docs/src/test/java/com/example/Hypermedia.java +++ b/docs/src/test/java/com/example/mockmvc/Hypermedia.java @@ -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; diff --git a/docs/src/test/java/com/example/InvokeService.java b/docs/src/test/java/com/example/mockmvc/InvokeService.java similarity index 93% rename from docs/src/test/java/com/example/InvokeService.java rename to docs/src/test/java/com/example/mockmvc/InvokeService.java index 097899f2..2eb8eeb5 100644 --- a/docs/src/test/java/com/example/InvokeService.java +++ b/docs/src/test/java/com/example/mockmvc/InvokeService.java @@ -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; diff --git a/docs/src/test/java/com/example/AlwaysDo.java b/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java similarity index 84% rename from docs/src/test/java/com/example/AlwaysDo.java rename to docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java index efc64f6c..ea6313cd 100644 --- a/docs/src/test/java/com/example/AlwaysDo.java +++ b/docs/src/test/java/com/example/mockmvc/ParameterizedOutput.java @@ -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[] + } diff --git a/docs/src/test/java/com/example/PathParameters.java b/docs/src/test/java/com/example/mockmvc/PathParameters.java similarity index 94% rename from docs/src/test/java/com/example/PathParameters.java rename to docs/src/test/java/com/example/mockmvc/PathParameters.java index c7810598..460d37db 100644 --- a/docs/src/test/java/com/example/PathParameters.java +++ b/docs/src/test/java/com/example/mockmvc/PathParameters.java @@ -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; diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/mockmvc/Payload.java similarity index 83% rename from docs/src/test/java/com/example/Payload.java rename to docs/src/test/java/com/example/mockmvc/Payload.java index 5ee1a890..bed9663f 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/mockmvc/Payload.java @@ -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[] } diff --git a/docs/src/test/java/com/example/PerTestPreprocessing.java b/docs/src/test/java/com/example/mockmvc/PerTestPreprocessing.java similarity index 90% rename from docs/src/test/java/com/example/PerTestPreprocessing.java rename to docs/src/test/java/com/example/mockmvc/PerTestPreprocessing.java index 504f96c0..60e56da1 100644 --- a/docs/src/test/java/com/example/PerTestPreprocessing.java +++ b/docs/src/test/java/com/example/mockmvc/PerTestPreprocessing.java @@ -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[] } diff --git a/docs/src/test/java/com/example/RequestParameters.java b/docs/src/test/java/com/example/mockmvc/RequestParameters.java similarity index 96% rename from docs/src/test/java/com/example/RequestParameters.java rename to docs/src/test/java/com/example/mockmvc/RequestParameters.java index 5a314958..eece3411 100644 --- a/docs/src/test/java/com/example/RequestParameters.java +++ b/docs/src/test/java/com/example/mockmvc/RequestParameters.java @@ -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> diff --git a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java new file mode 100644 index 00000000..4166c13e --- /dev/null +++ b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java @@ -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[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/CustomEncoding.java b/docs/src/test/java/com/example/restassured/CustomEncoding.java new file mode 100644 index 00000000..eb85c805 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/CustomEncoding.java @@ -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[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java new file mode 100644 index 00000000..68b8a88b --- /dev/null +++ b/docs/src/test/java/com/example/restassured/EveryTestPreprocessing.java @@ -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[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java b/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java new file mode 100644 index 00000000..c0943a77 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/ExampleApplicationTests.java @@ -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[] +} diff --git a/docs/src/test/java/com/example/restassured/HttpHeaders.java b/docs/src/test/java/com/example/restassured/HttpHeaders.java new file mode 100644 index 00000000..2556be02 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/HttpHeaders.java @@ -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[] + } +} diff --git a/docs/src/test/java/com/example/restassured/Hypermedia.java b/docs/src/test/java/com/example/restassured/Hypermedia.java new file mode 100644 index 00000000..2922377d --- /dev/null +++ b/docs/src/test/java/com/example/restassured/Hypermedia.java @@ -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)); + } + +} diff --git a/docs/src/test/java/com/example/restassured/InvokeService.java b/docs/src/test/java/com/example/restassured/InvokeService.java new file mode 100644 index 00000000..347c368e --- /dev/null +++ b/docs/src/test/java/com/example/restassured/InvokeService.java @@ -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[] + } +} diff --git a/docs/src/test/java/com/example/restassured/ParameterizedOutput.java b/docs/src/test/java/com/example/restassured/ParameterizedOutput.java new file mode 100644 index 00000000..10fd8be1 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/ParameterizedOutput.java @@ -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[] + +} diff --git a/docs/src/test/java/com/example/restassured/PathParameters.java b/docs/src/test/java/com/example/restassured/PathParameters.java new file mode 100644 index 00000000..81c7cc3e --- /dev/null +++ b/docs/src/test/java/com/example/restassured/PathParameters.java @@ -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[] + } +} diff --git a/docs/src/test/java/com/example/restassured/Payload.java b/docs/src/test/java/com/example/restassured/Payload.java new file mode 100644 index 00000000..ee6c2456 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/Payload.java @@ -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)); + } + +} diff --git a/docs/src/test/java/com/example/restassured/PerTestPreprocessing.java b/docs/src/test/java/com/example/restassured/PerTestPreprocessing.java new file mode 100644 index 00000000..69f4936e --- /dev/null +++ b/docs/src/test/java/com/example/restassured/PerTestPreprocessing.java @@ -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[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/RequestParameters.java b/docs/src/test/java/com/example/restassured/RequestParameters.java new file mode 100644 index 00000000..86f40c4b --- /dev/null +++ b/docs/src/test/java/com/example/restassured/RequestParameters.java @@ -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[] + } + +} diff --git a/settings.gradle b/settings.gradle index 7552e239..8d6619a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,5 @@ rootProject.name = 'spring-restdocs' include 'docs' include 'spring-restdocs-core' -include 'spring-restdocs-mockmvc' \ No newline at end of file +include 'spring-restdocs-mockmvc' +include 'spring-restdocs-restassured' \ No newline at end of file diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java similarity index 59% rename from spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractConfigurer.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java index f8effd0a..7a8a9e03 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java @@ -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 configuration, + RestDocumentationContext context); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java new file mode 100644 index 00000000..1463edde --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java @@ -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

The type of the configurer's parent + * @author Andy Wilkinson + */ +public abstract class AbstractNestedConfigurer

extends AbstractConfigurer implements + NestedConfigurer

{ + + 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; + } + +} diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/NestedConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java similarity index 72% rename from spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/NestedConfigurer.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java index 1c6768c8..72e2f41b 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/NestedConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java @@ -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 The parent's type + * @param

The parent's type * @author Andy Wilkinson */ -interface NestedConfigurer { +interface NestedConfigurer

{ /** * Returns the configurer's parent. * * @return the parent */ - PARENT and(); + P and(); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java new file mode 100644 index 00000000..bedec85a --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java @@ -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 The concrete type of the {@link SnippetConfigurer}. + * @param The concrete type of this configurer, to be returned from methods that + * support chaining + * @author Andy Wilkinson + */ +public abstract class RestDocumentationConfigurer { + + 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 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 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; + } + + } + +} diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/SnippetConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java similarity index 61% rename from spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/SnippetConfigurer.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java index 2723d016..6f4be5a5 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/SnippetConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java @@ -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

The type of the configurer's parent + * @param The concrete type of the configurer to be returned from chained methods * @author Andy Wilkinson */ -public class SnippetConfigurer extends - AbstractNestedConfigurer { +public abstract class SnippetConfigurer extends AbstractNestedConfigurer

{ + + /** + * 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 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 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; } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java index 5df99f6f..0670be1d 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java @@ -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; } diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentation.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentation.java index bedd3753..541f380d 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentation.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentation.java @@ -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); } /** diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java new file mode 100644 index 00000000..f9beacac --- /dev/null +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurer.java @@ -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 + 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 configurers; + + private ConfigurerApplyingRequestPostProcessor( + RestDocumentation restDocumentation, List 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 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; + } + } + +} diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractNestedConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcSnippetConfigurer.java similarity index 65% rename from spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractNestedConfigurer.java rename to spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcSnippetConfigurer.java index a4444e7c..a46302c0 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/AbstractNestedConfigurer.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcSnippetConfigurer.java @@ -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 The type of the configurer's parent * @author Andy Wilkinson */ -abstract class AbstractNestedConfigurer extends - AbstractConfigurer implements NestedConfigurer, MockMvcConfigurer { +public class MockMvcSnippetConfigurer extends + SnippetConfigurer + 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); } - } diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationMockMvcConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationMockMvcConfigurer.java deleted file mode 100644 index 20f06771..00000000 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationMockMvcConfigurer.java +++ /dev/null @@ -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. - *

- * In the absence of any {@link #snippets() customization} the following snippets will be - * produced by default: - *

    - *
  • {@link CurlDocumentation#curlRequest() Curl request}
  • - *
  • {@link HttpDocumentation#httpRequest() HTTP request}
  • - *
  • {@link HttpDocumentation#httpResponse() HTTP response}
  • - *
- *

- * In the absence of any {@link #uris() customization}, documented URIs have the following - * defaults: - *

    - *
  • Scheme: {@code http}
  • - *
  • Host: {@code localhost}
  • - *
  • Port: {@code 8080}
  • - *
- * - * @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; - } - - } - -} diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java index 425cd2fc..075c162f 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/RestDocumentationResultHandler.java @@ -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 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 configuration = (Map) 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 getSnippets(MvcResult result) { - List combinedSnippets = new ArrayList<>((List) result - .getRequest().getAttribute( - "org.springframework.restdocs.mockmvc.defaultSnippets")); + List combinedSnippets = new ArrayList<>( + (List) ((Map) result.getRequest().getAttribute( + "org.springframework.restdocs.configuration")) + .get(SnippetConfigurer.ATTRIBUTE_DEFAULT_SNIPPETS)); combinedSnippets.addAll(this.snippets); return combinedSnippets; } diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java index b7024fd4..b2c3a8c8 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/UriConfigurer.java @@ -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 { + AbstractNestedConfigurer 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 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); + } + } diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationConfigurerTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java similarity index 85% rename from spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationConfigurerTests.java rename to spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java index 486bfd8e..ad6ac269 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/RestDocumentationConfigurerTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationConfigurerTests.java @@ -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); diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index 76e3dd67..d444dd17 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -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")); diff --git a/spring-restdocs-restassured/build.gradle b/spring-restdocs-restassured/build.gradle new file mode 100644 index 00000000..e7c33f79 --- /dev/null +++ b/spring-restdocs-restassured/build.gradle @@ -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.*" +} \ No newline at end of file diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactory.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactory.java new file mode 100644 index 00000000..c3ef0b20 --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactory.java @@ -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 entry : requestSpec.getQueryParams().entrySet()) { + parameters.add(entry.getKey(), entry.getValue().toString()); + } + for (Entry entry : requestSpec.getRequestParams().entrySet()) { + parameters.add(entry.getKey(), entry.getValue().toString()); + } + for (Entry entry : requestSpec.getFormParams().entrySet()) { + parameters.add(entry.getKey(), entry.getValue().toString()); + } + return parameters; + } + + private Collection extractParts( + FilterableRequestSpecification requestSpec) { + List 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; + } +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationResponseFactory.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationResponseFactory.java new file mode 100644 index 00000000..bbcd251e --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredOperationResponseFactory.java @@ -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(); + } + +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentation.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentation.java new file mode 100644 index 00000000..447ba1f5 --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentation.java @@ -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); + } + +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurer.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurer.java new file mode 100644 index 00000000..5dbdb1c0 --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurer.java @@ -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 + implements Filter { + + private final RestAssuredSnippetConfigurer snippetConfigurer = new RestAssuredSnippetConfigurer( + this); + + private final List 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 configuration = new HashMap<>(); + filterContext.setValue("org.springframework.restdocs.configuration", + configuration); + for (AbstractConfigurer configurer : this.configurers) { + configurer.apply(configuration, context); + } + return filterContext.next(requestSpec, responseSpec); + } +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredSnippetConfigurer.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredSnippetConfigurer.java new file mode 100644 index 00000000..d91b38a1 --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredSnippetConfigurer.java @@ -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 + implements Filter { + + RestAssuredSnippetConfigurer(RestAssuredRestDocumentationConfigurer parent) { + super(parent); + } + + @Override + public Response filter(FilterableRequestSpecification requestSpec, + FilterableResponseSpecification responseSpec, FilterContext context) { + return and().filter(requestSpec, responseSpec, context); + } + +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java new file mode 100644 index 00000000..3460a99a --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestDocumentationFilter.java @@ -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 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 attributes = new HashMap<>(); + attributes.put(RestDocumentationContext.class.getName(), documentationContext); + attributes.put("org.springframework.restdocs.urlTemplate", + requestSpec.getUserDefinedPath()); + Map 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 getSnippets(Map configuration) { + List combinedSnippets = new ArrayList<>( + (List) 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; + } + + } + +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/RestAssuredPreprocessors.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/RestAssuredPreprocessors.java new file mode 100644 index 00000000..5e5c1f9f --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/RestAssuredPreprocessors.java @@ -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(); + } + +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessor.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessor.java new file mode 100644 index 00000000..bb134d0c --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessor.java @@ -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: + *
    + *
  • {@link OperationRequest#getUri() Request URI} + *
  • {@link OperationRequest#getHeaders() Request headers} + *
  • {@link OperationRequest#getContent() Request content} + *
  • {@link OperationRequestPart#getHeaders() Request part headers} + *
  • {@link OperationRequestPart#getContent() Request part content} + *
  • {@link OperationResponse#getHeaders() Response headers} + *
  • {@link OperationResponse#getContent() Response content} + *
+ * + * @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> header : headers.entrySet()) { + for (String value : header.getValue()) { + modified.add(header.getKey(), this.contentModifier.modify(value)); + } + } + return modified; + } + + private Collection modify(Collection parts) { + List 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 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 ""; + } + } +} diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/package-info.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/package-info.java new file mode 100644 index 00000000..83ffc61b --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/operation/preprocess/package-info.java @@ -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; + diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java new file mode 100644 index 00000000..a8b893fa --- /dev/null +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/package-info.java @@ -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; + diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactoryTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactoryTests.java new file mode 100644 index 00000000..bb532559 --- /dev/null +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredOperationRequestFactoryTests.java @@ -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 parts = request.getParts(); + assertThat(parts.size(), is(2)); + Iterator 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; + } + + } + +} diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java new file mode 100644 index 00000000..1f7bbbd4 --- /dev/null +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationConfigurerTests.java @@ -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 configurationCaptor = ArgumentCaptor.forClass(Map.class); + verify(this.filterContext).setValue( + eq("org.springframework.restdocs.configuration"), + configurationCaptor.capture()); + @SuppressWarnings("unchecked") + Map 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))); + } +} diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java new file mode 100644 index 00000000..3ae6d13f --- /dev/null +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java @@ -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, "\"<>\"")))).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\" : \"<>\"%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, "\"<>\""), modifyUris() + .scheme("https").host("api.example.com") + .removePort()))).get("/").then().statusCode(200); + String prettyPrinted = String.format("{%n \"a\" : \"<>\",%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> foo() { + Map 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\"}]}"; + } + + } + +} diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessorTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessorTests.java new file mode 100644 index 00000000..a962c782 --- /dev/null +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/operation/preprocess/UriModifyingOperationPreprocessorTests.java @@ -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.emptyList()); + } + + private OperationRequest createRequestWithContent(String content) { + return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, + content.getBytes(), new HttpHeaders(), new Parameters(), + Collections.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.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]); + } + +} diff --git a/spring-restdocs-restassured/src/test/resources/body.txt b/spring-restdocs-restassured/src/test/resources/body.txt new file mode 100644 index 00000000..1a010b1c --- /dev/null +++ b/spring-restdocs-restassured/src/test/resources/body.txt @@ -0,0 +1 @@ +file \ No newline at end of file diff --git a/spring-restdocs-restassured/src/test/resources/custom-snippet-templates/org/springframework/restdocs/templates/curl-request.snippet b/spring-restdocs-restassured/src/test/resources/custom-snippet-templates/org/springframework/restdocs/templates/curl-request.snippet new file mode 100644 index 00000000..07f3a48f --- /dev/null +++ b/spring-restdocs-restassured/src/test/resources/custom-snippet-templates/org/springframework/restdocs/templates/curl-request.snippet @@ -0,0 +1 @@ +Custom curl request \ No newline at end of file