spring-projects / spring-restdocs

Test-driven documentation for RESTful services
https://spring.io/projects/spring-restdocs
Apache License 2.0
1.16k stars 736 forks source link

Provide better diagnostics when a descriptor is missing a property required by the snippet template #678

Open FloHin opened 4 years ago

FloHin commented 4 years ago

Background: I removed some method calls to "description()" to better debug my Rest tests, big mistake.

Now I understand the exception, but I needed to debug it, to understand that not the "description" of my entity was meant, but that "FieldDescriptor for path '$my_variable_name' has no description"

UseCase: As a developer, I want the exception to show me what field exactly is meant.

I understand that this is not a "bug report", but merely a minor proposal to improve "developer happiness", as it took me really a long time to figure out what was wrong

Reproduce:

Instead of

...
fieldWithPath(prefix + "input_data_type").type(JsonFieldType.STRING).description("DataType for input data")
...

forget the description

...
fieldWithPath(prefix + "input_data_type").type(JsonFieldType.STRING)
...

Exception:

No key, method or field with name 'description' on line 7
org.springframework.restdocs.mustache.MustacheException$Context: No key, method or field with name 'description' on line 7
    at org.springframework.restdocs.mustache.Mustache$VariableSegment.execute(Mustache.java:789)
    at org.springframework.restdocs.mustache.Template$1.execute(Template.java:131)
    at org.springframework.restdocs.mustache.Template$1.execute(Template.java:124)
    at org.springframework.restdocs.mustache.Template$Fragment.execute(Template.java:59)
    at org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda.execute(AsciidoctorTableCellContentLambda.java:36)
    at org.springframework.restdocs.mustache.Mustache$SectionSegment.execute(Mustache.java:856)
    at org.springframework.restdocs.mustache.Mustache$BlockSegment.executeSegs(Mustache.java:827)
    at org.springframework.restdocs.mustache.Mustache$SectionSegment.execute(Mustache.java:848)
    at org.springframework.restdocs.mustache.Template.executeSegs(Template.java:114)
    at org.springframework.restdocs.mustache.Template.execute(Template.java:91)
    at org.springframework.restdocs.mustache.Template.execute(Template.java:82)
    at org.springframework.restdocs.templates.mustache.MustacheTemplate.render(MustacheTemplate.java:62)
    at org.springframework.restdocs.snippet.TemplatedSnippet.document(TemplatedSnippet.java:82)
    at org.springframework.restdocs.generate.RestDocumentationGenerator.handle(RestDocumentationGenerator.java:191)
    at org.springframework.restdocs.mockmvc.RestDocumentationResultHandler.handle(RestDocumentationResultHandler.java:52)
    at org.springframework.test.web.servlet.MockMvc$1.andDo(MockMvc.java:201)
    at com.mlreef.rest.api.DataProcessorApiTest.Can retrieve all DataProcessors filtered(DataProcessorApiTest.kt:110)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
FloHin commented 4 years ago

If maintainers think this is a good idea, I would fork and work on that, happy to contribute.

wilkinsona commented 4 years ago

Thanks for reporting this, @FloHin. There's definitely room for improvement here. Thanks to the flexibility of the template-based approach, I'm not sure how precisely the problem can be described. However, I'm pretty sure that it could be better than it currently is. If you have some time to work on some improvements, a pull request would be great. Thanks!

wilkinsona commented 4 years ago

Unfortunately, when a failure like this occurs, JMustache does not provide enough information to identify exactly which field did not have a description. The best that can be done is to provide a little bit more context, including the template that was being rendered, when the failure occurred. Something like this:

org.springframework.restdocs.templates.TemplateRenderingException: Failed to render 'request-fields'. No value for 'description' on line 7:

  1. |===
  2. |Path|Type|Description
  3. 
  4. {{#fields}}
  5. |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
  6. |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
  7. |{{#tableCellContent}}{{description}}{{/tableCellContent}}
  8. 
  9. {{/fields}}
 10. |===

    at org.springframework.restdocs.templates.mustache.MustacheTemplate.render(MustacheTemplate.java:95)
    at org.springframework.restdocs.snippet.TemplatedSnippet.document(TemplatedSnippet.java:82)
    at org.springframework.restdocs.payload.RequestFieldsSnippetTests.missingDescription(RequestFieldsSnippetTests.java:58)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: org.springframework.restdocs.mustache.MustacheException$Context: No key, method or field with name 'description' on line 7
    at org.springframework.restdocs.mustache.Mustache$VariableSegment.execute(Mustache.java:789)
    at org.springframework.restdocs.mustache.Template$1.execute(Template.java:131)
    at org.springframework.restdocs.mustache.Template$1.execute(Template.java:124)
    at org.springframework.restdocs.mustache.Template$Fragment.execute(Template.java:59)
    at org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda.execute(AsciidoctorTableCellContentLambda.java:36)
    at org.springframework.restdocs.mustache.Mustache$SectionSegment.execute(Mustache.java:856)
    at org.springframework.restdocs.mustache.Mustache$BlockSegment.executeSegs(Mustache.java:827)
    at org.springframework.restdocs.mustache.Mustache$SectionSegment.execute(Mustache.java:848)
    at org.springframework.restdocs.mustache.Template.executeSegs(Template.java:114)
    at org.springframework.restdocs.mustache.Template.execute(Template.java:91)
    at org.springframework.restdocs.mustache.Template.execute(Template.java:82)
    at org.springframework.restdocs.templates.mustache.MustacheTemplate.render(MustacheTemplate.java:84)
    ... 34 more
wilkinsona commented 4 years ago

It may be possible to improve on the above using a Lambda or by implementing CustomContext. Both will require changes that are too broad for a 2.0.x release.