Documenting API request validation and data constraints with Java Spring REST Docs

Setting Up Spring REST Docs

First, we need to set up Spring REST Docs in our Java Spring project. Add the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <version>2.0.5.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-asciidoctor</artifactId>
    <version>2.0.5.RELEASE</version>
    <scope>test</scope>
</dependency>

Next, create a test configuration class that enables Spring REST Docs and sets up the documentation output directory:

@TestConfiguration
public class ApiDocumentationConfig {

    @Bean
    public RestDocumentationResultHandler documentationHandler() {
        return MockMvcRestDocumentation.document("{ClassName}/{methodName}",
                Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()));
    }
    
    // Other test-related bean configurations
}

Documenting Request Validation and Data Constraints

To document request validation and data constraints, we need to create test cases that verify the expected behavior of the API endpoint. Here’s an example of a test case for request validation using JUnit and Mockito:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.restdocs.snippet.Attributes;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
@ContextConfiguration(classes = ApiDocumentationConfig.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ApiIntegrationTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private RestDocumentationResultHandler documentationHandler;

    private MockMvc mockMvc;

    @BeforeEach
    void setUp(RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(MockMvcRestDocumentation.document("{ClassName}/{methodName}",
                        Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                        Preprocessors.preprocessResponse(Preprocessors.prettyPrint())))
                .build();
    }
    
    @Test
    void testRequestValidation() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.post("/api/endpoint")
                .content("{ \"foo\": \"value\", \"bar\": 42 }")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isBadRequest())
                .andDo(documentationHandler.document(
                        PayloadDocumentation.requestFields(
                                PayloadDocumentation.fieldWithPath("foo")
                                        .description("The foo field")
                                        .attributes(Attributes.key("constraints").value("Required")),
                                PayloadDocumentation.fieldWithPath("bar")
                                        .description("The bar field")
                                        .attributes(Attributes.key("constraints").value("Required, Numeric"))
                        ),
                        PayloadDocumentation.responseFields(
                                PayloadDocumentation.fieldWithPath("message")
                                        .description("The error message")
                        )
                ));
    }
    
    // Other test cases for data constraints

}

In this example, we are testing a POST request to an API endpoint and validating the request payload. The usage of PayloadDocumentation.fieldWithPath() allows us to provide a description and attributes to each field, such as constraints like Required or Numeric. We also use PayloadDocumentation.responseFields() to document the expected response fields.

After running the test cases, Spring REST Docs generates documentation in the desired format (e.g., HTML or AsciiDoc), providing clear instructions for API request validation and data constraints.

Conclusion

Properly documenting API request validation and data constraints is crucial for both API developers and consumers. With Java Spring REST Docs, we can easily generate comprehensive documentation that ensures the integrity of our API payloads and enforces data constraints. By following the steps outlined in this blog post, you can start documenting and validating your API requests effectively. Happy coding!

#Java #SpringRESTDocs #API #Documentation