Skip to content

Commit f8411a7

Browse files
cnsgithubmduesterhoeft
authored andcommitted
Add REST Assured Support (#59)
Add convenience wrapper to be able to migrate rest assured based tests to restdocs-api-spec
1 parent aa604ed commit f8411a7

File tree

15 files changed

+979
-409
lines changed

15 files changed

+979
-409
lines changed

README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ This is why we came up with this project.
4545
- [Build configuration](#build-configuration)
4646
- [Gradle](#gradle)
4747
- [Maven](#maven)
48-
- [Usage with Spring REST Docs](#usage-with-spring-rest-docs)
48+
- [Usage with Spring REST Docs - MockMvc](#usage-with-spring-rest-docs---mockmvc)
49+
- [Usage with Spring REST Docs - RestAssured](#usage-with-spring-rest-docs---rest-assured)
4950
- [Documenting Bean Validation constraints](#documenting-bean-validation-constraints)
5051
- [Migrate existing Spring REST Docs tests](#migrate-existing-spring-rest-docs-tests)
5152
- [Security Definitions in OpenAPI](#security-definitions-in-openapi)
@@ -134,7 +135,7 @@ See the [build.gradle](samples/restdocs-api-spec-sample/build.gradle) for the se
134135
The root project does not provide a maven plugin.
135136
But you can find a plugin that works with `restdocs-api-spec` at [BerkleyTechnologyServices/restdocs-spec](https://github.com/BerkleyTechnologyServices/restdocs-spec).
136137

137-
### Usage with Spring REST Docs
138+
### Usage with Spring REST Docs - MockMvc
138139

139140
The class [ResourceDocumentation](restdocs-api-spec/src/main/kotlin/com/epages/restdocs/apispec/ResourceDocumentation.kt) contains the entry point for using the [ResourceSnippet](restdocs-api-spec/src/main/kotlin/com/epages/restdocs/apispec/ResourceSnippet.kt).
140141

@@ -219,6 +220,28 @@ This makes the `urlTemplate` available in the snippet and we can depend on the n
219220
mockMvc.perform(get("/carts/{id}", cartId)
220221
```
221222

223+
### Usage with Spring REST Docs - REST Assured
224+
The usage for REST Assured is similar to MockMVC, except that [com.epages.restdocs.apispec.RestAssuredRestDocumentationWrapper](restdocs-api-spec/src/main/kotlin/com/epages/restdocs/apispec/RestAssuredRestDocumentationWrapper.kt) is used instead of [com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper](restdocs-api-spec/src/main/kotlin/com/epages/restdocs/apispec/MockMvcRestDocumentationWrapper.kt).
225+
226+
To use the ``RestAssuredRestDocumentationWrapper``, you have to add a dependency to [restdocs-api-spec-restassured](restdocs-api-spec-restassured) to your build.
227+
```java
228+
RestAssured.given(this.spec)
229+
.filter(RestAssuredRestDocumentationWrapper.document("{method-name}",
230+
"The API description",
231+
requestParameters(
232+
parameterWithName("param").description("the param")
233+
),
234+
responseFields(
235+
fieldWithPath("doc.timestamp").description("Creation timestamp")
236+
)
237+
))
238+
.when()
239+
.queryParam("param", "foo")
240+
.get("/restAssuredExample")
241+
.then()
242+
.statusCode(200);
243+
```
244+
222245
### Documenting Bean Validation constraints
223246

224247
Similar to the way Spring REST Docs allows to use [bean validation constraints](https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-constraints) to enhance your documentation, you can also use the constraints from your model classes to let `restdocs-api-spec` enrich the generated JsonSchemas.
@@ -438,8 +461,3 @@ See [openapi2raml.gradle](samples/restdocs-api-spec-sample/openapi2raml.gradle).
438461
./gradlew -b samples/restdocs-api-spec-sample/openapi2raml.gradle openapi2raml
439462
```
440463

441-
## Limitations
442-
443-
### Rest Assured
444-
445-
Spring REST Docs also supports REST Assured to write tests that produce documentation. We currently have not tried REST Assured with our project.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
3+
plugins {
4+
kotlin("jvm")
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
maven { url = uri("https://jitpack.io") }
10+
}
11+
12+
val springBootVersion: String by extra
13+
val springRestDocsVersion: String by extra
14+
val junitVersion: String by extra
15+
16+
dependencies {
17+
compile(kotlin("stdlib-jdk8"))
18+
19+
compile(project(":restdocs-api-spec"))
20+
compile("org.springframework.restdocs:spring-restdocs-mockmvc:$springRestDocsVersion")
21+
22+
testCompile("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
23+
exclude("junit")
24+
}
25+
testCompile("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
26+
testImplementation("org.junit-pioneer:junit-pioneer:0.2.2")
27+
testCompile("org.springframework.boot:spring-boot-starter-hateoas:$springBootVersion")
28+
}
29+
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.epages.restdocs.apispec
2+
3+
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
4+
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler
5+
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor
6+
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor
7+
import org.springframework.restdocs.snippet.Snippet
8+
import java.util.function.Function
9+
10+
/**
11+
* Convenience class to migrate to restdocs-openapi in a non-invasive way.
12+
* It is a wrapper and replacement for MockMvcRestDocumentation that transparently adds a ResourceSnippet with the descriptors provided in the given snippets.
13+
*/
14+
object MockMvcRestDocumentationWrapper : RestDocumentationWrapper() {
15+
16+
@JvmOverloads @JvmStatic
17+
fun document(
18+
identifier: String,
19+
resourceDetails: ResourceSnippetDetails,
20+
requestPreprocessor: OperationRequestPreprocessor? = null,
21+
responsePreprocessor: OperationResponsePreprocessor? = null,
22+
snippetFilter: Function<List<Snippet>, List<Snippet>> = Function.identity(),
23+
vararg snippets: Snippet
24+
): RestDocumentationResultHandler {
25+
26+
val enhancedSnippets =
27+
enhanceSnippetsWithResourceSnippet(
28+
resourceDetails = resourceDetails,
29+
snippetFilter = snippetFilter,
30+
snippets = *snippets
31+
)
32+
33+
if (requestPreprocessor != null && responsePreprocessor != null) {
34+
return MockMvcRestDocumentation.document(
35+
identifier,
36+
requestPreprocessor,
37+
responsePreprocessor,
38+
*enhancedSnippets
39+
)
40+
} else if (requestPreprocessor != null) {
41+
return MockMvcRestDocumentation.document(identifier, requestPreprocessor, *enhancedSnippets)
42+
} else if (responsePreprocessor != null) {
43+
return MockMvcRestDocumentation.document(identifier, responsePreprocessor, *enhancedSnippets)
44+
}
45+
46+
return MockMvcRestDocumentation.document(identifier, *enhancedSnippets)
47+
}
48+
49+
@JvmOverloads @JvmStatic
50+
fun document(
51+
identifier: String,
52+
description: String? = null,
53+
summary: String? = null,
54+
privateResource: Boolean = false,
55+
deprecated: Boolean = false,
56+
requestPreprocessor: OperationRequestPreprocessor? = null,
57+
responsePreprocessor: OperationResponsePreprocessor? = null,
58+
snippetFilter: Function<List<Snippet>, List<Snippet>> = Function.identity(),
59+
vararg snippets: Snippet
60+
): RestDocumentationResultHandler {
61+
return document(
62+
identifier = identifier,
63+
resourceDetails = ResourceSnippetParametersBuilder()
64+
.description(description)
65+
.summary(summary)
66+
.privateResource(privateResource)
67+
.deprecated(deprecated),
68+
requestPreprocessor = requestPreprocessor,
69+
responsePreprocessor = responsePreprocessor,
70+
snippetFilter = snippetFilter,
71+
snippets = *snippets
72+
)
73+
}
74+
75+
@JvmStatic
76+
fun document(
77+
identifier: String,
78+
requestPreprocessor: OperationRequestPreprocessor,
79+
vararg snippets: Snippet
80+
): RestDocumentationResultHandler {
81+
return document(identifier, null, null, false, false, requestPreprocessor, snippets = *snippets)
82+
}
83+
84+
@JvmStatic
85+
fun document(
86+
identifier: String,
87+
description: String,
88+
privateResource: Boolean,
89+
vararg snippets: Snippet
90+
): RestDocumentationResultHandler {
91+
return document(identifier, description, null, privateResource, snippets = *snippets)
92+
}
93+
94+
@JvmStatic
95+
fun resourceDetails(): ResourceSnippetDetails {
96+
return ResourceSnippetParametersBuilder()
97+
}
98+
}
Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.epages.restdocs.apispec
22

3+
import com.epages.restdocs.apispec.ResourceDocumentation.resource
34
import org.assertj.core.api.Assertions.assertThatCode
5+
import org.assertj.core.api.BDDAssertions.then
46
import org.junit.jupiter.api.Test
57
import org.junit.jupiter.api.extension.ExtendWith
68
import org.springframework.beans.factory.annotation.Autowired
79
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
10+
import org.springframework.hateoas.MediaTypes.HAL_JSON
11+
import org.springframework.http.MediaType.APPLICATION_JSON
812
import org.springframework.restdocs.headers.HeaderDocumentation.headerWithName
913
import org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders
1014
import org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders
@@ -20,10 +24,14 @@ import org.springframework.restdocs.request.RequestDocumentation.pathParameters
2024
import org.springframework.test.context.junit.jupiter.SpringExtension
2125
import org.springframework.test.web.servlet.MockMvc
2226
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
27+
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document
28+
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post
29+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
30+
import java.io.File
2331

2432
@ExtendWith(SpringExtension::class)
2533
@WebMvcTest
26-
class MockMvcRestDocumentationWrapperIntegrationTest(@Autowired mockMvc: MockMvc) : ResourceSnippetIntegrationTest(mockMvc) {
34+
class MockMvcRestDocumentationWrapperIntegrationTest(@Autowired private val mockMvc: MockMvc) : ResourceSnippetIntegrationTest() {
2735

2836
@Test
2937
fun should_document_both_restdocs_and_resource() {
@@ -68,6 +76,81 @@ class MockMvcRestDocumentationWrapperIntegrationTest(@Autowired mockMvc: MockMvc
6876
thenSnippetFileExists()
6977
}
7078

79+
@Test
80+
fun should_document_request() {
81+
givenEndpointInvoked()
82+
83+
whenResourceSnippetDocumentedWithoutParameters()
84+
85+
thenSnippetFileExists()
86+
}
87+
88+
@Test
89+
fun should_document_request_with_description() {
90+
givenEndpointInvoked()
91+
92+
whenResourceSnippetDocumentedWithDescription()
93+
94+
thenSnippetFileExists()
95+
}
96+
97+
@Test
98+
fun should_document_request_with_fields() {
99+
givenEndpointInvoked()
100+
101+
whenResourceSnippetDocumentedWithRequestAndResponseFields()
102+
103+
thenSnippetFileExists()
104+
}
105+
106+
@Test
107+
fun should_document_request_with_null_field() {
108+
givenEndpointInvoked("null")
109+
110+
assertThatCode { this.whenResourceSnippetDocumentedWithRequestAndResponseFields() }
111+
.doesNotThrowAnyException()
112+
}
113+
114+
private fun whenResourceSnippetDocumentedWithoutParameters() {
115+
resultActions
116+
.andDo(document(operationName, resource()))
117+
}
118+
119+
private fun whenResourceSnippetDocumentedWithDescription() {
120+
resultActions
121+
.andDo(document(operationName, resource("A description")))
122+
}
123+
124+
private fun whenResourceSnippetDocumentedWithRequestAndResponseFields() {
125+
resultActions
126+
.andDo(document(operationName, buildFullResourceSnippet()))
127+
}
128+
129+
private fun givenEndpointInvoked(flagValue: String = "true") {
130+
resultActions = mockMvc.perform(
131+
post("/some/{someId}/other/{otherId}", "id", 1)
132+
.contentType(APPLICATION_JSON)
133+
.header("X-Custom-Header", "test")
134+
.accept(HAL_JSON)
135+
.content("""{
136+
"comment": "some",
137+
"flag": $flagValue,
138+
"count": 1
139+
}""".trimIndent()
140+
)
141+
).andExpect(status().isOk)
142+
}
143+
144+
private fun thenSnippetFileExists() {
145+
with(generatedSnippetFile()) {
146+
then(this).exists()
147+
val contents = readText()
148+
then(contents).isNotEmpty()
149+
}
150+
}
151+
152+
private fun generatedSnippetFile() = File("build/generated-snippets", "$operationName/resource.json")
153+
71154
@Throws(Exception::class)
72155
private fun whenDocumentedWithRestdocsAndResource() {
73156
resultActions

0 commit comments

Comments
 (0)