.consumeWith(document("buplication/associateWithBook",
requestFields(
fieldWithPath("asdf").description("TODO add")
),
responseFields(
PUBLICATION_FIELD_DESCRIPTORS
)
))
My current code:
.consumeWith(document("testCases/associateWithRelease",
requestFields(
attributes(key("title").value("single url to Release"))
),
responseFields(
TESTCASE_FIELD_DESCRIPTORS
)
))
is still throwing exception... partial stacktrace:
org.springframework.restdocs.payload.PayloadHandlingException: Cannot handle text/uri-list content as it could not be parsed as JSON or XML
at org.springframework.restdocs.payload.ContentHandler.forContentWithDescriptors(ContentHandler.java:69)
at org.springframework.restdocs.payload.AbstractFieldsSnippet.createModel(AbstractFieldsSnippet.java:157)
at org.springframework.restdocs.snippet.TemplatedSnippet.document(TemplatedSnippet.java:78)
at org.springframework.restdocs.generate.RestDocumentationGenerator.handle(RestDocumentationGenerator.java:191)
at org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.lambda$document$0(WebTestClientRestDocumentation.java:77)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultBodySpec.lambda$consumeWith$4(DefaultWebTestClient.java:437)
at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:206)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultBodySpec.consumeWith(DefaultWebTestClient.java:437)
I do not expect it. Is it due to use of requestFields(...)
? Perhaps it does not make sense to generate any request-parameters.adoc
, when I do not have ability to document its content... Do you have any suggestion? My goal is to have some sentence about request parameters (actually about whole request body) included in documentation in exactly same way, as I include snippet for json type request...
operation::someEndpoint/someUsecase[snippets='http-request']
I am trying to run a JUnit 5 test with RestDocs for a webflux app using WebTestClient using @WebFluxTest, @AutoConfigureRestDocs, @ExtendWith(RestDocumentation.class, SpringExtension.class) with WebTestClient autowired.
If I have 1 @Test with restdocs document calls it works fine, but when I add a 2nd rest docs test to show a different example of calling the service I get failures during the maven test phase.
For test #1:
java.lang.IllegalStateException: Context already exists. Did you forget to call afterTest()?
at org.springframework.restdocs.ManualRestDocumentation.beforeTest(ManualRestDocumentation.java:70)
at org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener$DocumentationHandler.beforeTestMethod(RestDocsTestExecutionListener.java:66)
...
For test #2:
java.lang.NullPointerException
at org.springframework.restdocs.ManualRestDocumentation.beforeOperation(ManualRestDocumentation.java:85)
at org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer.createConfiguration(WebTestClientRestDocumentationConfigurer.java:73)
...
So I guess my question is 2 part. Is it possible to have more than 1 document tests in 1 test class for WebTestClient with WebFlux or does it have to be 1 to 1?
If you can have multiple, then what do I need to do to get the tests to pass consistently for RestDocs?
Hi everyone! I'm currently trying to document the type of the array elements in json response. I use standalone MockMvc configuration and mock target service. The idea is to have a mechanism that alerts, if the endpoint contract has changed. One thing in that is to control changes of types.
It is not a problem to realize it against named response fields like this:
{
person: {
name: "some_name",
age: 33,
married: false,
hobbies: ["movies", "coocking", "dancing"]
}
}
fieldWithPath("['person']").type(JsonFieldtype.OBJECT).description(...)
fieldWithPath("['person']['name']").type(JsonFieldtype.STRING).description(...)
fieldWithPath("['person']['age']).type(JsonFieldtype.NUMBER).description(...)
fieldWithPath("['person']['married']").type(JsonFieldtype.BOOLEAN).description(...)
fieldWithPath("['person']['hobbies']").type(JsonFieldtype.ARRAY).description(...)
If someone adds a new field or delete any field in root object or in "person" object, the test will fail and enforce one to update the documentation.
The same will happen if someone changes the type of field, for ex. age: 33 -> age: "33".
The same will happen if someone changes the type of elements in "hobbies" to object like
{
/* ... */,
hobbies: [
{name: "name", communitySite: "site"},
/*...*/
]
}
In this case the exception will say that there are some undocumented fields in response json.
In this case the solution is in documenting like this:
fieldWithPath("['person']['hobbies'][]['name']").type(JsonFieldtype.STRING).description(...)
fieldWithPath("['person']['hobbies'][]['communitySite']").type(JsonFieldtype.STRING).description(...)
The question is how can I control the simple types of array elements in json response?
For example, if the interaction has changed and it was decided to pass not a hobbie name, but an id of hobbie. So the response will have been changed to:
{
/* ... */,
hobbies: [123, 91, 87]
}
In this case we surely need an exception to be thrown to be aware of need to update the text documentation.
Need some help. Thanks a lot in advance.
I would expect that to result in an exception as there are no longer and objects within the
hobbies
array withname
fields.
Sure, that would, if the initial state was {name: "name", communitySite: "site"}. But we currently have the array with ids (JsonFieldType.NUMBER) and need to be informed if the type will have been changed. Is there any way to do that?
Hi Team
[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
X-time-stamp: 2020-07-24T22:49:15.456146500
X-all-media: All Media
Content-Type: application/json
Content-Length: 21
{
"2020" : true
}
----
I have this response in one of my RestDocs tests. I need to add the documentation for the fields, I tried something like 'year' but it failed.
Here is the endpoint definition
@GetMapping("/check")
public Mono<Map<Integer, Boolean>> checkYear(@RequestParam Integer leapYear) {
return Mono.just(leapYear).map(transformLeapYearToMap());
}
NOTE: This entry is for one item, another sample follows, showing a list of years.
[source,options="nowrap"]
----
[ {
"1600" : true
}, {
"1700" : false
}, {
"1800" : false
}, {
"1900" : false
}, {
"2000" : true
}, {
"2019" : false
}, {
"2020" : true
} ]
----
Here is the endopoint definition.
@GetMapping("/check/years")
public Flux<Map<Integer, Boolean>> checkYears(@RequestParam List<Integer> leapYears) {
return Flux.fromIterable(leapYears).map(transformLeapYearToMap());
}
Thanks!
[].*
would be the full path I think. In the first case, it'd just be *
.
_links
as ignored because even though I explicitly document links
restdocs still wants me to document the "JSON" part. Do we misuse the API? It's feeling kinda unintuitive this way.
I'm new to spring-restdocs and am trying to integrate it into my existing project.
I'm generating my snippets with rest assured in the integration tests. With maven and the failsafe plugin that defaults to the verify
phase. Problem is, that asciidoctor runs before that in prepare-package
.
If I configure asciidoctor to run at post-integration-test
, it succeeds, but then the finished html page is not in my jar, cause that was already created in the package
phase.
I wonder if there is a best practice I'm ignoring, which would enable me to run the tests, compile the documentation and then package everything in a jar in the correct order.
hello I try the write a base
class for Contracts
for a API with Webflux
. and RestDocs Snippets
I can't get generate the snippets
I try already a lot of combinations. maybe somebody have any idea or this done already ?
And also It don't load the rest-messages.properties
where I deine some HATEOAS
title stuff.
https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#getting-started-sample-applications
https://docs.spring.io/spring-framework/docs/5.0.x/spring-framework-reference/testing.html#webtestclient
Base Class:
@ExtendWith(RestDocumentationExtension::class, SpringExtension::class)
@SpringBootTest(classes = [QuickStartApplication::class], webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = ["server.port=8080"])
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
abstract class BaseClass {
@Autowired
lateinit var context: ApplicationContext
private var webTestClient: WebTestClient? = null
@BeforeEach
fun setUp(restDocumentation: RestDocumentationContextProvider?) {
webTestClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.build()
}
}
To get it green
my test I have to comment it out the title
definitions. I have already up&running the same stack with WebMVC
there it just works fine :(.
Contracts Groovy
"_links": [
"self" : [
"href" : value(
consumer("http://${fromRequest().header('Host')}/api/contact-n-support"),
producer("http://localhost:8080/api/contact-n-support"))
// "title": value(
// consumer("Aktuelle Resource"),
// producer("Aktuelle Resource"))
target
├── classes
├── generated-sources
├── generated-test-sources
├── maven-status
├── stubs
├── surefire-reports
└── test-classes
The outputDirectory
are null
Hi all: Is there a way to have multiple document
calls for a single response? If not, what is the recommended way of making multiple pieces of documentation/tables for a response with nested objects?
{
"weather": {
"wind": {
"speed": 15.3,
"direction": 287.0
},
"temperature": {
"high": 21.2,
"low": 14.8
}
}
}
e.g. using the above weather
example from the documentation, how would one create docs for both wind
and temperature
in a single test?
Hi all,
I was wondering what most people consider of being the best practice between these 2 different approaches:
- design first - write your API spec up front - maybe using tools such as stoplight.io - that you implement the API. To make sure the implementation follows your specification you can use tools such as
swagger-request-validator
- test-driven - you describe and introspect your API in your test using
spring-restdocs
- you can use the extensionrestdocs-api-spec
to also generate an openAPI specification for your API - so you rather generate the specification from the knowledge you put into your testt
Partially quoting a post from 2018 here... so maybe we have a more general sense of what might be the better practise.
I'm using the first approach, and have no experience with the test-driven / spring-restdocs way.
Atm I use the openapi spec during pre-development to discuss what we are planning to build with the analists. To define models and operations + document them. And we start from there generating code (interfaces and DTO's mostly), from the API specs. (analogous to ws-import in SOAP) And I like the possibility to work this way. So both analists en software architects are on the same page.
So I wonder: Can I work the same way if I switch to the TDD way, using spring-restdocs?
Hello is it possible now to create RestDocs
now from a WebFlux Setup
?
Here is a sample project https://github.com/marzelwidmer/kboot-template
The pom.xml
I add the follwoing :
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>
my BaseClass
Looks like follow
@ExtendWith(RestDocumentationExtension::class, SpringExtension::class)
@SpringBootTest(
classes = [DemoApplicationService::class], webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = ["spring.main.web-application-type=reactive", "server.port=8080"]
)
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
@WithMockCustomUser(username = "jane@doe.ch", authorities = ["keepcalm.admin", "keepcalm.user"], firstname = "jane", lastname = "doe")
abstract class BaseClass {
@Autowired
private lateinit var applicationContext: ApplicationContext
private lateinit var webTestClient: WebTestClient
@BeforeEach
fun setUp(restDocumentation: RestDocumentationContextProvider?) {
runBlocking {
webTestClient = WebTestClient.bindToApplicationContext(applicationContext)
.configureClient()
.filter(
documentationConfiguration(restDocumentation)
.operationPreprocessors()
.withRequestDefaults(prettyPrint())
.withResponseDefaults(prettyPrint())
)
.build()
}
}
}
tree -L 2
.
└── target
├── classes
├── generated-sources
├── generated-test-sources
├── maven-status
└── test-classes
I'm migrating my project from Maven to Gradle and after I did, when I call gradle cleanTest test
I started receiving the following error message from my test:
java.lang.NoClassDefFoundError: org/springframework/restdocs/ManualRestDocumentation
at org.springframework.boot.test.autoconfigure.restdocs.RestDocumentationContextProviderRegistrar.registerBeanDefinitions(RestDocumentationContextProviderRegistrar.java:41)
at org.springframework.context.annotation.ImportBeanDefinitionRegistrar.registerBeanDefinitions(ImportBeanDefinitionRegistrar.java:86)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:396)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:723)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:395)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:157)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564)
Here my gradle config for rest-docs:
plugins {
// Docs
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
configurations {
asciidoctorExt
}
dependencies {
asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.5.RELEASE"
testCompileOnly 'org.springframework.restdocs:spring-restdocs-webtestclient'
}
ext {
snippetsDir = file("build/generated-snippets")
}
test {
outputs.dir snippetsDir
useJUnitPlatform ()
}
What I might be doing wrong?
testCompile
while what is really working is testImplementation
:dependencies {
asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.5.RELEASE"
testImplementation 'org.springframework.restdocs:spring-restdocs-webtestclient'
}
testCompileOnly
there rather than testCompile
. The latter will add the dependency to the test runtime classpath whereas the former won't. I guess the problem was that the version of Gradle that you're using doesn't support testCompile
at all. The correct replacement is testImplementation
as you discovered. There's some information about this in Gradle's documentation on upgrading: https://docs.gradle.org/current/userguide/upgrading_version_6.html#sec:configuration_removal
Hi everyone
In a JUnit 5 test, I'm trying to document my Spring Boot application API with the following:
this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext)
.configureClient()
.filter(documentationConfiguration(restDocumentation)
.operationPreprocessors()
.withResponseDefaults(prettyPrint()))
.build();
(...)
webTestClient
.post().uri("/tokens/{id}/random?size=16", TOKEN_TEST)
.exchange()
.expectStatus().isOk()
.expectBody(RandomResponse.class)
.consumeWith(entityExchangeResult -> assertThat(entityExchangeResult.getResponseBody().getRandomBytes()).hasSize(16))
.consumeWith(document(
"get_random_bytes_as_json",
pathParameters(
parameterWithName("id").description("Unique identifier of the token"),
parameterWithName("size").description("Size of the random byte array")
)));
and I'm facing the following exception:
org.springframework.restdocs.snippet.SnippetException: Path parameters with the following names were not found in the request: [size]
at org.springframework.restdocs.request.PathParametersSnippet.verificationFailed(PathParametersSnippet.java:149)
at org.springframework.restdocs.request.AbstractParametersSnippet.verifyParameterDescriptors(AbstractParametersSnippet.java:108)
at org.springframework.restdocs.request.AbstractParametersSnippet.createModel(AbstractParametersSnippet.java:74)
Could someone help me to see what I'm doing wrong?
Thank you a lot for taking the time to read me
Éric
RequestDocumentation#requestParameters(org.springframework.restdocs.request.ParameterDescriptor...)
which will do the job.Got it !
Everything becomes clear when you read the documentation with attention :
.consumeWith(document(
"get_random_bytes_as_json",
pathParameters(
parameterWithName("id").description("Unique identifier of the token")
),
requestParameters(
parameterWithName("size").description("Size of the random byte array")
))
)
Hope this helps
Hi,
I have created a pull request for this issue: spring-projects/spring-restdocs#462
My local build worked out fine, but the CI build (spring-projects/spring-restdocs#797) shows an error result that I don't understand.
It would be great to get some inside on this, so that I can adapt my code.
Thanks for the PR, @achimgrimm. What command did you run when your local build worked out fine? If I check out your changes and run ./gradlew build
it fails for me. The first problem is code formatting, which can be fixed by running ./gradlew format
. Running ./gradlew build
again then fails due to a problem reported by Checkstyle:
[ant:checkstyle] [ERROR] /Users/awilkinson/dev/spring-projects/spring-restdocs/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractBodySnippet.java:78:83: Ternary operation missing parentheses. Use the form "(a != b) ? y : n" [SpringTernary]
There are also some Checkstyle problems with the test code too:
[ant:checkstyle] [ERROR] /Users/awilkinson/dev/spring-projects/spring-restdocs/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java:34:72: Using the '.*' form of import should be avoided - org.springframework.restdocs.payload.PayloadDocumentation.*. [AvoidStarImport]
[ant:checkstyle] [ERROR] /Users/awilkinson/dev/spring-projects/spring-restdocs/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseBodySnippetTests.java:35:15: Unused import - org.springframework.restdocs.payload.PayloadDocumentation.requestBody. [UnusedImports]