no

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 generat...

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>

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 

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();
  }
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: 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

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

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.

6.3 Example Response

Related

spring-testing 2158932175066637085

Post a Comment Default Comments

item