Guide to Test a Spring Boot App

Learn how to test a Spring boot web application. We will see some very quick examples (using Junit 5) and configurations for :

  • Verifying that the application has been initialized successfully
  • Unit testing REST Controller with @WebMvcTest
  • Unit testing Service Layer with Mockito
  • Unit testing DAO Layer with @DataJpaTest and @AutoConfigureTestDatabase
  • Integration testing using @SpringBootTest
  • System testing using RestTemplate

For demo purposes, we have created a very simple Employee management application. It has a few CRUD API calls for creating, fetching and deleting the employees from the database.

Do not forget to use @ExtendWith(SpringExtension.class) to run the tests.

1. Maven

This demo application uses Spring boot 2 and Java 15. It has mysql-connector-java to connect with the database for MySQL DB-related operations.

It includes the auto-configuration from the following modules:

  • spring-boot-starter-web
  • spring-boot-starter-validation
  • spring-boot-starter-test with Junit 5
  • spring-boot-starter-data-jpa
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.4.5</version>
  <relativePath /> 
</parent>
 
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>15</java.version>
</properties>
 
<dependencies>
 
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
 
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
 
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
 
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
  </dependency>
 
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
      <exclusion>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
 
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <scope>test</scope>
  </dependency>
 
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <scope>test</scope>
  </dependency>
 
  <dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
  </dependency>
 
</dependencies>

2. Testing if Application Loads Correctly

This is the simplest of all. Write a Test class annotated with @SpringBootTest and check for any important eagerly initialized bean if it has been successfully injected into an auto-wired attribute or not.

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.howtodoinjava.employees.controllers.EmployeeController;
 
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class EmployeesApplicationTests {
 
  @Autowired
  EmployeeController employeeController;
 
  @Test
  public void contextLoads() {
    Assertions.assertThat(employeeController).isNot(null);
  }
}

3. Unit Testing the REST Controller

Write a Test class annotated with @WebMvcTest. We can specify which Controller we want to test in the annotation value itself.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import java.util.Arrays;
import java.util.List;
 
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
 
import com.howtodoinjava.employees.model.Employee;
import com.howtodoinjava.employees.services.EmployeeService;
 
@ExtendWith(SpringExtension.class)
@WebMvcTest(EmployeeController.class)
public class StandaloneControllerTests {
 
  @MockBean
  EmployeeService employeeService;
 
  @Autowired
  MockMvc mockMvc;
 
  @Test
  public void testfindAll() throws Exception {
    Employee employee = new Employee("Lokesh", "Gupta");
    List<Employee> employees = Arrays.asList(employee);
 
    Mockito.when(employeeService.findAll()).thenReturn(employees);
 
    mockMvc.perform(get("/employee"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", Matchers.hasSize(1)))
        .andExpect(jsonPath("$[0].firstName", Matchers.is("Lokesh")));
  }
 
}

4. Unit Testing the Service Layer

To unit test the service layer, we must use mock the DAO layer. Then we can run the tests using MockitoExtension.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
import java.util.ArrayList;
import java.util.List;
 
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
 
import com.howtodoinjava.employees.dao.EmployeeRepository;
import com.howtodoinjava.employees.model.Employee;
 
@ExtendWith(MockitoExtension.class)
public class ServiceTests 
{
  @InjectMocks
    EmployeeService service;
      
    @Mock
    EmployeeRepository dao;
     
    @Test
    public void testFindAllEmployees()
    {
        List<Employee> list = new ArrayList<Employee>();
        Employee empOne = new Employee("John", "John");
        Employee empTwo = new Employee("Alex", "kolenchiski");
        Employee empThree = new Employee("Steve", "Waugh");
          
        list.add(empOne);
        list.add(empTwo);
        list.add(empThree);
          
        when(dao.findAll()).thenReturn(list);
          
        //test
        List<Employee> empList = service.findAll();
          
        assertEquals(3, empList.size());
        verify(dao, times(1)).findAll();
    }
     
    @Test
    public void testCreateOrSaveEmployee()
    {
        Employee employee = new Employee("Lokesh","Gupta");
          
        service.save(employee);
          
        verify(dao, times(1)).save(employee);
    }
}

5. Unit testing DAO / Repository Layer

To unit test the DAO layer, we first need an in-memory test database. This we can achieve using @AutoConfigureTestDatabase.

Then we need to use @DataJpaTest which disables full auto-configuration and instead apply only configuration relevant to JPA tests.

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
 
import com.howtodoinjava.employees.model.Employee;
 
@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class DaoTests {
 
  @Autowired
  EmployeeRepository employeeRepository;
 
  @Test
  public void testCreateReadDelete() {
    Employee employee = new Employee("Lokesh", "Gupta");
 
    employeeRepository.save(employee);
 
    Iterable<Employee> employees = employeeRepository.findAll();
    Assertions.assertThat(employees).extracting(Employee::getFirstName).containsOnly("Lokesh");
 
    employeeRepository.deleteAll();
    Assertions.assertThat(employeeRepository.findAll()).isEmpty();
  }
}

6. Integration Testing

Integration tests cover the whole path through the application. In these tests, we send a request to the application and check that it responds correctly and has changed the database state according to our expectations.

The database can be an actual physical database or in-memory database for testing purposes.

import javax.validation.ValidationException;
 
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
 
import com.howtodoinjava.employees.model.Employee;
 
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class IntegrationTests {
 
  @Autowired
  EmployeeController employeeController;
 
  @Test
  public void testCreateReadDelete() {
    Employee employee = new Employee("Lokesh", "Gupta");
 
    Employee employeeResult = employeeController.create(employee);
 
    Iterable<Employee> employees = employeeController.read();
    Assertions.assertThat(employees).first().hasFieldOrPropertyWithValue("firstName", "Lokesh");
 
    employeeController.delete(employeeResult.getId());
    Assertions.assertThat(employeeController.read()).isEmpty();
  }
 
  @Test
  public void errorHandlingValidationExceptionThrown() {
 
    Assertions.assertThatExceptionOfType(ValidationException.class)
        .isThrownBy(() -> employeeController.somethingIsWrong());
  }
}

7. System Testing using RestTemplate

We can use RestTemplate class to perform system testing. It helps in verifying the application as it looks to the client outside the application.

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
 
import com.howtodoinjava.employees.model.Employee;
 
public class SystemTests {
 
  @Test
  public void testCreateReadDelete() {
    RestTemplate restTemplate = new RestTemplate();
 
    String url = "http://localhost:8080/employee";
 
    Employee employee = new Employee("Lokesh", "Gupta");
    ResponseEntity<Employee> entity = restTemplate.postForEntity(url, employee, Employee.class);
 
    Employee[] employees = restTemplate.getForObject(url, Employee[].class);
    Assertions.assertThat(employees).extracting(Employee::getFirstName).containsOnly("Lokesh");
 
    restTemplate.delete(url + "/" + entity.getBody().getId());
    Assertions.assertThat(restTemplate.getForObject(url, Employee[].class)).isEmpty();
  }
 
  @Test
  public void testErrorHandlingReturnsBadRequest() {
 
    RestTemplate restTemplate = new RestTemplate();
 
    String url = "http://localhost:8080/wrong";
 
    try {
      restTemplate.getForEntity(url, String.class);
    } catch (HttpClientErrorException e) {
      Assertions.assertThat(e.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }
  }
}

8. Conclusion

This Spring boot 2.4 testing tutorial is to provide a short example of how to configure various dependencies; as well as write various kinds of tests using simple examples.

Feel free to modify the above-given code snippets as your requirements.

Happy Learning !!

Download Source Code

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.

Our Blogs

REST API Tutorial