How to integrate Spring REST Docs with Javadocs
Learn how to properly and continuously document your Spring REST API with Spring REST Docs.
1. Introduction
There are many ways to generate Spring REST docs. One popular way is by using Swagger, but in this blog, I will share an alternative through Spring REST Docs.
This tutorial requires a project with the following stack:
- Spring REST
- Maven
2. Spring REST Docs
Spring REST Docs generate documentation from an Asciidoctor template and auto-generated snippets produced with Spring MVC tests. Unlike Swagger, it uses JavaDocs for class, method, and parameter definitions, including constraints.
One advantage of using this library is that missing a parameter definition (request, path, etc.) will break the integration tests. So you can only really proceed and make a release after fixing them first. Thus, it always produced updated, concise, and well-structured documentation.
2.1 Spring REST Docs Dependencies
<dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>capital.scalable</groupId> <artifactId>spring-auto-restdocs-core</artifactId> <scope>test</scope> </dependency>
3. Let's Start Documenting our REST API
3.1 Our REST API
/** * Creates, and maps an SSO user to an internal user table. * * @param platformUserInboundDto - JSON object * @return a CompletableFuture instance of {@link PlatformUserOutboundDto} */ @PostMapping( path = EndpointConstants.PATH_USERS, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Transactional public CompletableFuture<PlatformUserOutboundDto> mapUpdateOrCreateIfAbsent( @Valid @RequestBody PlatformUserInboundDto platformUserInboundDto) { log.debug("Check if user with externalRef={} exists", platformUserInboundDto.getExternalRef()); return platformSsoService.createIfNotExists(web2ServiceMapper.toPlatformUser(platformUserInboundDto)) .thenApply(service2WebMapper::toPlatformUserOutboundDto); }
For this exercise, let's assume we have an endpoint that creates or maps an SSO user if it doesn't exist in the local system. It accepts an object parameter that contains the user information. Above the method is the JavaDoc.
Here's the input DTO which as we can see is annotated with constraints.
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class PlatformUserInboundDto { /** * Unique id from the SSO provider. */ @NotNull @NotEmpty @Size(max = 50) private String externalRef; @NotNull @NotEmpty @Size(max = 255) private String email; @Size(max = 50) private String identityProvider; @Size(max = 255) private String firstName; @Size(max = 255) private String lastName; //.. }
3.2 Our REST API Test
// In your test class define and initialize the MockMvc object on every test. Thus, we need to create a method annotated with @BeforeEach protected MockMvc mockMvc; @BeforeEach void setup(RestDocumentationContextProvider restDocumentation) { this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .alwaysDo(JacksonResultHandlers.prepareJackson(objectMapper)) .apply( MockMvcRestDocumentation.documentationConfiguration(restDocumentation) .uris() .withScheme("http") .withHost("localhost") .withPort(8080) .and() .snippets() .withDefaults( curlRequest(), httpRequest(), httpResponse(), requestFields().failOnUndocumentedFields(false), responseFields(), pathParameters().failOnUndocumentedParams(true), requestParameters().failOnUndocumentedParams(true), description(), methodAndPath(), links(), embedded(), sectionBuilder() .snippetNames( SnippetRegistry.AUTO_METHOD_PATH, SnippetRegistry.AUTO_DESCRIPTION, SnippetRegistry.AUTO_PATH_PARAMETERS, SnippetRegistry.AUTO_REQUEST_PARAMETERS, SnippetRegistry.AUTO_REQUEST_FIELDS, SnippetRegistry.HTTP_REQUEST, SnippetRegistry.CURL_REQUEST, SnippetRegistry.HTTP_RESPONSE, SnippetRegistry.AUTO_EMBEDDED, SnippetRegistry.AUTO_LINKS) .skipEmpty(true) .build())) .build(); }The actual test.
String endpoint = "users"; String call = "signOnSuccess200"; MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(EndpointConstants.PATH_USERS) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(platformUserInboundDto)); MvcResult mvcResult = mockMvc .perform(request) .andDo( document( endpoint + "/" + call, preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()))) .andExpect(status().isOk()) .andExpect(request().asyncStarted()) .andDo(document(endpoint + "/" + call, preprocessRequest(prettyPrint()))) .andReturn(); mockMvc .perform(asyncDispatch(mvcResult)) .andDo(document(endpoint + "/" + call, preprocessResponse(prettyPrint())));
4. AsciiDoctor Template
:doctype: book :icons: font :source-highlighter: highlightjs :toc: left :toc-title: Index :toclevels: 4 :sectlinks: :sectnums: :sectnumlevels: 5 :page-layout: docs = IAM Services Documentation This is the REST API documentation for User Services. [[Signon]] == SSO Signon and Platform User Creation include::{snippets}/users/signOnSuccess200/auto-section.adoc[]
5. Running the Integration Tests
6. Example of Generated Documentation
6.1 Spring REST Docs Snippets
6.2 REST Documentation
The description of this endpoint comes from JavaDoc. See the DTO from 3.1.
6.2 Example Requests
The request details are the values we use in our test case.
Post a Comment