Documentation and Code Coverage

Documentation and Code Coverage

Spring Application Deployed with Kubernetes

Step by step building an application using Spring Boot and deployed via Docker on Kubernetes with Helm

full course
  1. Setup: IDE and New Project
  2. Create the Data Repository
  3. Building a Service Layer
  4. Create a REST Controller
  5. Logging, Tracing and Error Handling
  6. Documentation and Code Coverage
  7. Database as a Service
  8. Containerize the Service With Docker
  9. Docker Registry
  10. Automated Build Pipeline
  11. Helm for Deployment
  12. Setting up a Kubernetes Cluster
  13. Automating Deployment (for CICD)
  14. System Design
  15. Messaging and Event Driven Design
  16. Web UI with React
  17. Containerizing our UI
  18. UI Build Pipeline
  19. Put the UI in to Helm
  20. Creating an Ingress in Kubernetes
  21. Simplify Deployment
  22. Conclusion and Review

Before we move on to getting our service ready for deployment we need to do a little more housecleaning. Sometimes these tasks get de-prioritized so we might as well take care of it now, before we move on.

Endpoint Documentation

Using OpenAPI is a powerful tool for documenting our REST endpoints. Documenting our endpoints reduces the amount of unplanned time we need to take from our priorities to support our users or clients of our service. Additionally, it can also be used to generate the clients, so that we don’t have to build and maintain client libraries.

First we need to add the dependencies to our maven pom file.

<properties>
   ...
   <springdoc-openapi-core.version>1.1.49</springdoc-openapi-core.version>
</properties>
<dependencies>
...
   <!-- documentation -->
   <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-ui</artifactId>
      <version>${springdoc-openapi-core.version}</version>
   </dependency>
...
</dependencies>

Restart our application and go to http://localhost:10000/swagger-ui/. You should see the swagger ui and you can see our endpoint. You can also confirm on the CustomerDTO schema that the validation requirements are documented.

Code Coverage

We really need to be aware of how our code is evolving. One particularly useful metric is code coverage. We’ll be following this example to configure our project to generate code coverage reports via jacoco.

Add this configuration to the pom.xml file:

<properties>
   ...
   <jacoco-maven-plugin.version>0.8.5</jacoco-maven-plugin.version>
</properties>
<build>
   <plugins>
      ...
      <plugin>
         <groupId>org.jacoco</groupId>
         <artifactId>jacoco-maven-plugin</artifactId>
         <version>${jacoco-maven-plugin.version}</version>
         <executions>
            <execution>
               <goals>
                  <goal>prepare-agent</goal>
               </goals>
            </execution>
            <execution>
               <id>report</id>
               <phase>prepare-package</phase>
               <goals>
                  <goal>report</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
   </plugins>

</build>

And build the report with mvn clean test jacoco:report. View the report at /target/site/jacoco/index.html. Its should look something like this:

Which looks kind of bad, but its actually failing because of all of the generated Lombok code (as well as some classes we shouldn’t be testing). Lets exclude that from the report. Finally, we see that the controller advice as well as the config package are being analyzed. Lets move controller advice to config and exclude the config directory. Additionally, lets exclude the spring boot Application class and the exceptions from the jacoco-maven-plugin maven plugin definition.

</execution>
   </executions>
   <configuration>
      <excludes>
         <exclude>com/brianrook/medium/customer/config/**/*</exclude>
         <exclude>com/brianrook/medium/customer/*Application*</exclude>
         <exclude>com/brianrook/medium/customer/exception/**/*</exclude>
      </excludes>
   </configuration>
</plugin>

Rerunning our report looks much better, but we can see two issues. One is that the code generated by mapstruct has some untested paths. We can and should write tests for these because mapping classes are high risk. If you look at the report closely, you can see that we don’t have test cases around null input. These tests are easily written.

package com.brianrook.medium.customer.controller.mapper;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CustomerDTOMapperTest {
    @Test
    public void testNullCustomer(){
assertThat(CustomerDTOMapper.INSTANCE.customerToCustomerDTO(null)).isNull();
    }
    @Test
    public void testNullCustomerDTO(){
assertThat(CustomerDTOMapper.INSTANCE.customerDTOToCustomer(null)).isNull();
    }
}
package com.brianrook.medium.customer.dao.mapper;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CustomerEntityMapperTest {
    @Test
    public void testNullCustomer(){
assertThat(CustomerEntityMapper.INSTANCE.customerToCustomerEntity(null)).isNull();
    }
    @Test
    public void testNullCustomerDTO(){
assertThat(CustomerEntityMapper.INSTANCE.customerEntityToCustomer(null)).isNull();
    }
}

More importantly, we can see that the service class has an untested exception path. We could easily write a unit test here that mocks the DAO and throws an exception. I would say: please don’t do that! This is a path that we added for a specific behavior, so lets create a behavior test. Add this additional test to CustomerControllerTest:

@Test
public void testAddCustomerConflict() throws URISyntaxException
{
    CustomerEntity savedRecord = CustomerEntity.builder()
            .firstName("test")
            .lastName("user")
            .email("[email protected]")
            .phoneNumber("(123)654-7890")
            .build();
    customerDAO.save(savedRecord);

    RestTemplate restTemplate = new RestTemplate();
    String baseUrl = "http://localhost:"+randomServerPort+customerPath;
    URI uri = new URI(baseUrl);
    CustomerDTO customerDTO = CustomerDTO.builder()
            .firstName(savedRecord.getFirstName())
            .lastName(savedRecord.getLastName())
            .email(savedRecord.getEmail())
            .phoneNumber(savedRecord.getPhoneNumber())
            .build();

    HttpHeaders headers = new HttpHeaders();
    headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

    HttpEntity<CustomerDTO> request = new HttpEntity<>(customerDTO, headers);

    try {
        ResponseEntity<CustomerDTO> result = restTemplate.postForEntity(uri, request, CustomerDTO.class);

    }catch (HttpServerErrorException e) {
        //Verify request failed
        Assertions.assertEquals(500, e.getRawStatusCode());
    }
}

Rerun the report. We should have 100% coverage now. The best part is it only took 3 test classes and 6 tests to get there.

Build and Commit

git checkout -b coverage
mvn clean install
git add .
git commit -m "OpenAPI docs and code coverage"
git push --set-upstream origin coverage
git checkout master
git merge coverage
git push

0 comments on “Documentation and Code CoverageAdd yours →

Leave a Reply

Your email address will not be published.