DEV Community

Ed Legaspi
Ed Legaspi

Posted on • Edited on • Originally published at czetsuyatech.com

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 really proceed and make a release without fixing them first. Thus, it produced always 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>
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;

  //..
}
Enter fullscreen mode Exit fullscreen mode

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 @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();
  }
Enter fullscreen mode Exit fullscreen mode

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())));
Enter fullscreen mode Exit fullscreen mode

4. AsciiDoctor Template

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

: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[]
Enter fullscreen mode Exit fullscreen mode

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. No need for extra annotation like Swagger.

6.1 Spring REST Docs Snippets

Image description

6.2 REST Documentation

Image description

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

6.2 Example Requests

Image description

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

6.3 Example Response

Image description

Top comments (0)