Creating a REST API using Spring Boot + Tests + Documentation [part 04]
Francisco Pereira
Posted on February 1, 2023
Hello!
I'm back in this tutorial to teach you how to develop a REST API using Spring Boot. As I said earlier, this tutorial is divided into 5 parts as shown in the following table:
Part | Content |
---|---|
Part 01 | Application Startup |
Part 02 | Settings and Database |
Part 03 | API Implementation |
Part 04 | Tests + Coverage |
Part 05 | Swagger Documentation |
It is expected that from this fourth part you will learn the following:
- Use of Junit
- Creation of Unit Tests
- Creation of Integration Tests
- Using Fakers to create test data
Remember that in this tutorial the following tools are used:
- JDK 17
- IntelliJ
- Maven
- Insomnia
- MySQL database
Step 01 - Creating test properties
Usually when we have to test our application, we create a properties file dedicated to the tests. This is done to have a better organization of development and test environments.
So, inside resources
you can create a new file called application-test.properties
which will contain the following content:
spring.datasource.url=jdbc:mysql://localhost:3307/small_library_test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
The first three items are responsible of the database connection. Note that a new database (small_library_test
) has been created for the tests but if you want, you can use the same database used for development.
The last item is present to avoid the LazyInitializationException
exception.
Therefore, when we create our tests, they will be using the properties defined in this new file.
Step 02 - Exception Handlers
By default, Spring handles some exceptions through classes, such as EntityNotFoundException
and EmptyResultDataAccessException
.
The first case happens when the user tries to search for an object in the database that does not exist, for example they would like to find a book whose id is equal to 9999999999 (thinking about the case in which this id does not exist in the database). By default, it returns error 500 (bad request) along with the exception body.
But let's think that in this case instead of returning the error 500, we would like to return the error 404 (Not Found). How can we carry out this modification?
That's why there is the RestControllerAdvice
, it is used for handling exceptions in controllers. For its creation, you'll have to go to the src.main.java.com.simplelibrary.simplelibrary.API
package and create a new package called exception
. And inside this, create a class called ErrorHandler
:
package com.simplelibrary.simplelibraryAPI.exception;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.simplelibrary.simplelibraryAPI.dto.ErrorValidatorDTO;
@RestControllerAdvice
public class ErrorHandler {
@ExceptionHandler({EntityNotFoundException.class, EmptyResultDataAccessException.class})
public ResponseEntity error404(){
return ResponseEntity.notFound().build();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity error400(MethodArgumentNotValidException exception){
var erros = exception.getFieldErrors();
return ResponseEntity.badRequest().body(erros.stream().map(ErrorValidatorDTO::new).toList());
}
}
In order for Spring to recognize that class as a Rest Controller Advice, you must place the @RestControllerAdvice
annotation before the class initialization.
For each exception you want to handle, you must put it inside the @ExceptionHandler
annotation saying that that exception will be handled inside the next method.
Note that the error404
method will handle exceptions that normally throw the 500 error and cause them to throw the 404 error.
The error400
method will handle the exception MethodArgumentNotValidException. This exception is triggered when there is a validation error, for example, when the user tries to register a book without its title. Originally, the response he will receive is as follows:
{
"timestamp": "2023-01-27T19:04:03.372+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity com.simplelibrary.simplelibraryAPI.controller.BookController.store(com.simplelibrary.simplelibraryAPI.dto.BookRequestDTO,org.springframework.web.util.UriComponentsBuilder) throws java.io.IOException: [Field error in object 'bookRequestDTO' on field 'title': rejected value [null]; codes [NotBlank.bookRequestDTO.title,NotBlank.title,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [bookRequestDTO.title,title]; arguments []; default message [title]]; default message [não deve estar em branco]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:144)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:181)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:148)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1010)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:913)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:884)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
"message": "Validation failed for object='bookRequestDTO'. Error count: 1",
"errors": [
{
"codes": [
"NotBlank.bookRequestDTO.title",
"NotBlank.title",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"bookRequestDTO.title",
"title"
],
"arguments": null,
"defaultMessage": "title",
"code": "title"
}
],
"defaultMessage": "must not be blank",
"objectName": "bookRequestDTO",
"field": "title",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
],
"path": "/books"
}
Realize the amount of information that is sent to the user, when in fact we just want them to know which field is with the error and what the error message is. Therefore, we will carry out your treatment through a DTO.
Inside the src.main.java.com.simplelibrary.simplelibrary.API.dto
package we will create a record called ErrorValidatorDTO
:
package com.simplelibrary.simplelibraryAPI.dto;
import org.springframework.validation.FieldError;
public record ErrorValidatorDTO(String field, String message){
public ErrorValidatorDTO(FieldError error){
this(error.getField(), error.getDefaultMessage());
}
}
From there we will get the error object and return to the user only the necessary information:
[
{
"field": "title",
"message": "must not be blank"
}
]
Step 03 - Book Factory
For our tests it will be necessary to create several books with different information. Carrying out this manual work is a bit exhausting, so we can use mechanisms created by the community.
One of the mechanisms we will use is Faker
. From it, we will be able to generate random values for the fields and create our book object. For that, you'll need to import the following dependency into your pom.xml
file:
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
By doing this we can create the BookFactory
class that will be responsible for creating book objects (DAO and DTO request models):
Inside the src.main.java.com.simplelibrary.simplelibrary.API.factory
package we will create a class called BookFakerFactory
:
package com.simplelibrary.simplelibraryAPI.factory;
import com.github.javafaker.Faker;
import com.simplelibrary.simplelibraryAPI.dto.BookRequestDTO;
import com.simplelibrary.simplelibraryAPI.model.Book;
public class BookFakerFactory {
private Faker faker;
public BookFakerFactory(){
this.faker = new Faker();
}
public BookRequestDTO createBook(){
var book = new BookRequestDTO(faker.book().title(), faker.book().author(),
faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
faker.random().nextBoolean());
return book;
}
public BookRequestDTO createBook(Book bookCreated){
var book = new BookRequestDTO(bookCreated);
return book;
}
public Book createBookModel(){
var bookDTO = new BookRequestDTO(faker.book().title(), faker.book().author(),
faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
faker.random().nextBoolean());
var book = new Book(bookDTO);
return book;
}
public BookRequestDTO createBookIncomplete(){
var book = new BookRequestDTO(faker.book().title(), "",
faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
faker.random().nextBoolean());
return book;
}
}
Note that we have 4 methods and the objective of all is to create an object that will later be used in tests.
- The
createBook
method will create aBookRequestDTO
object that will be used to test requests. - The
createBook
method (with parameter) will create aBookRequestDTO
object from a previously created book. - The
createBookModel
method will create aBook
object. - The
createBookIncomplete
method will create aBookRequestDTO
object without author information.
Note that all methods use the Faker
library.
Step 04 - Unit Tests
We are ready to create our tests. If you look inside the test
package, there is already a class called SimplelibraryApiApplicationTests
:
package com.simplelibrary.simplelibraryAPI;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SimplelibraryApiApplicationTests {
@Test
void contextLoads() throws Exception {
}
}
It will use JUnit
for testing.
Note some annotations that are important:
-
@SpringBootTest
indicates that that class will contain methods that should be tested. -
@Test
indicates that that method should be run as a test.
So we can create our first unit test. This one will test some features of our repository: inside the src.main.test.java.com.simplelibrary.simplelibrary.API
package we will create a package called unit
and inside it a class called BookTest
:
package com.simplelibrary.simplelibraryAPI.unit;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import com.simplelibrary.simplelibraryAPI.service.BookService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
@SpringBootTest
@ActiveProfiles("test")
public class BookTest {
@Autowired
private BookRepository repository;
@Autowired
private BookService service;
private BookFakerFactory bookFakerFactory;
public BookTest() {
this.bookFakerFactory = new BookFakerFactory();
}
}
Notice the @ActiveProfiles("test")
annotation. It is used to inform Spring that we are using the properties defined inside the application-test.properties
file.
We can think of some tests to be done:
Test for successfully creating a book.
Test to verify if the service of collecting the rating of a given book is working correctly (verifying that the rating value is populated when it is from an existing book and that it is not populated with a non-existing book).
The class with all the tests looks like this:
package com.simplelibrary.simplelibraryAPI.unit;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import com.simplelibrary.simplelibraryAPI.service.BookService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
@SpringBootTest
@ActiveProfiles("test")
public class BookTest {
@Autowired
private BookRepository repository;
@Autowired
private BookService service;
private BookFakerFactory bookFakerFactory;
public BookTest() {
this.bookFakerFactory = new BookFakerFactory();
}
@Test
public void itShouldCreateABookSuccessfully(){
var book = bookFakerFactory.createBookModel();
repository.save(book);
var bookFinder = repository.findBookByisbn(book.getIsbn());
Assertions.assertThat(book).isEqualTo(bookFinder);
}
@Test
public void itShouldFillTheRatingValueFromExistingBook() throws IOException {
var book = bookFakerFactory.createBookModel();
book.setTitle("Harry Potter and the Philosopher's Stone");
var bookCreated = service.store(bookFakerFactory.createBook(book));
assertThat(bookCreated.getRate()).isNotNull()
.isNotZero()
.isGreaterThan(Double.valueOf(0));
}
@Test
public void itShouldNotFillTheRatingValueFromNonexistentBook() throws IOException {
var book = bookFakerFactory.createBookModel();
book.setTitle("blablablablablabla");
var bookCreated = service.store(bookFakerFactory.createBook(book));
assertThat(bookCreated.getRate()).isZero();
}
}
Note the use of the assertThat
method present in JUnit, which allows checking values.
Step 05 - Integration tests
We can now create the integration test that will allow us to test our system's routes. We will also check if the requests are returned correctly and if the http codes also match expectations.
Inside the src.main.test.java.com.simplelibrary.simplelibrary.API
package we will create a package called integration
and inside it a class called BookControllerTest
:
package com.simplelibrary.simplelibraryAPI.integration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.assertj.core.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private BookRepository repository;
private BookFakerFactory bookFakerFactory;
public BookControllerTest() {
this.bookFakerFactory = new BookFakerFactory();
}
}
Note the use of a new annotation: @AutoConfigureMockMvc
from it we will be able to use the injected MockMvc
class to make requests to our server.
We can think of the following test cases:
- Verification if a book is successfully created
- Verification if a book is not created when we don't send all the necessary fields (http code 400)
- Verification if all books are successfully listed
- Verification if the information of a single book appears successfully.
- Verification if a book's information is changed successfully
- Verification that a book's information is not successfully changed when its information is not submitted.
- Verification if a book is successfully deleted.
- Verification that a non-existent book is not deleted.
The class with all implemented methods looks like this:
package com.simplelibrary.simplelibraryAPI.integration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.assertj.core.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private BookRepository repository;
private BookFakerFactory bookFakerFactory;
public BookControllerTest() {
this.bookFakerFactory = new BookFakerFactory();
}
@Test
void testIfBookIsCreatedSuccessfully() throws Exception {
var book = this.bookFakerFactory.createBook();
var result = this.mockMvc.perform(post("/books")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isCreated())
.andReturn();
String content = result.getResponse().getContentAsString();
assertNotNull(content);
}
@Test
void testIfBookIsNotCreatedWithMissingAttributes() throws Exception{
var book = this.bookFakerFactory.createBookIncomplete();
this.mockMvc.perform(post("/books")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isBadRequest())
.andReturn();
}
@Test
void testIfAllBooksAreListedSuccessfully() throws Exception{
this.mockMvc.perform(get("/books"))
.andExpect(status().isOk())
.andReturn();
}
@Test
void testIfSingleBookInformationIsShown() throws Exception {
this.mockMvc.perform(get("/books"))
.andExpect(status().isOk())
.andReturn();
}
@Test
void testIfBookIsUpdatedSuccessfully() throws Exception{
var book = this.bookFakerFactory.createBookModel();
var bookCreated = repository.save(book);
book.setAuthor("New Author name");
var book2 = this.bookFakerFactory.createBook(book);
this.mockMvc.perform(put("/books/{id}",bookCreated.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book2)))
.andExpect(status().isOk())
.andReturn();
var bookUpdated = repository.getReferenceById(bookCreated.getId());
assertThat(bookUpdated.getId()).isEqualTo(bookCreated.getId());
assertThat(bookUpdated.getAuthor()).isNotEqualTo(bookCreated.getAuthor());
}
@Test
void testIfBookIsNotUpdatedSuccessfully() throws Exception{
var book = this.bookFakerFactory.createBookModel();
var bookCreated = repository.save(book);
this.mockMvc.perform(put("/books/{id}",bookCreated.getId()))
.andExpect(status().isBadRequest())
.andReturn();
}
@Test
void testIfBookIsDeletedSuccessfully() throws Exception{
var book = this.bookFakerFactory.createBookModel();
var bookCreated = repository.save(book);
this.mockMvc.perform(delete("/books/"+bookCreated.getId()))
.andExpect(status().isNoContent())
.andReturn();
}
@Test
void testIfBookIsNotDeletedSuccessfully() throws Exception{
this.mockMvc.perform(delete("/books/999999"))
.andExpect(status().isNotFound())
.andReturn();
}
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Note the use of the asJsonString
method. It is used to take all the attribute values of an object and transform it into a Json string.
Step 06 - Running the tests
To run the tests you will have to go to the terminal and type the following command:
\.mvnw test
By doing this you will inform maven that you want to run all the tests in your application:
If all tests run successfully, you will see the following:
Step 07 - Test Coverage
Finally, many times when we do tests we would like to understand how broad it was. We would like to reach as many classes and methods as possible in our project, thus avoiding letting any part go unnoticed.
We can use the JaCoCo plugin to check test coverage. Its documentation is present at this link.
To use it, you'll need to go to your pom.xml
file and add the following plugin
:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
Just that! Now you can rerun your tests and when they are finished a .html
file will be generated inside target.site.jacoco
. The file is index.html
and when accessing it you will have a screen similar to this one:
As you can see, the com.simplelibrary.simplelibraryAPI.model
package had 70% of coverage. For more information, you can click on the package and then on the desired class to have the following coverage view:
- Green means that the code snippet was called.
Therefore, you can use the plugin and add new tests so that the coverage value of a given class is as high as possible!
What we learned in this tutorial
In this third part, we learned how to perform unit and integration tests using JUnit. We also saw how we can check test coverage using the JaCoCo plugin.
In the next part you will see about project documentation using Swagger.
Remembering that the complete project can be found HERE
Posted on February 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.