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't proceed and make a release without 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.

@Dat
@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?

We will follow the Spring MockMvc test here https://spring.io/guides/gs/testing-web.

// In your test class define and initialize the MockMvc object on every test. Thus, we need to create a method annotated with @BeforeEac
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

Create a file under /src/main/asciidoc named index.adoc and add the following content.

:doctype: boo
: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

To run the integration tests, execute the maven command:?mvn clean install verify.

What happens, then? When you run the integration tests, Spring REST Docs creates snippets with details from the JavaDoc, like descriptions and constraints. These snippets are then inserted in the Asciidoctor template.

6. Example of Generated Documentation

This documentation is generated every time you run the integration test. Thus, it always gets the latest information from JavaDocs. You don't need extra annotation like Swagger.

6.1 Spring REST Docs Snippets

No alt text provided for this image

6.2 REST Documentation

No alt text provided for this image

The description of this endpoint comes from JavaDoc. See the DTO from 3.1.

6.2 Example Requests

No alt text provided for this image

The request details are the values we use in our test case.

6.3 Example Response

No alt text provided for this image

This article is originally published at https://www.czetsuyatech.com/2022/04/spring-rest-docs-integration-with-javadocs.html.

要查看或添加评论,请登录

Ed Legaspi的更多文章

社区洞察

其他会员也浏览了