Learn how to test the different parts of 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 initialize and inject the mocks using Mockito in the test classes.
1. Maven
This demo application uses Spring Boot 3 and Java 17. It includes the auto-configuration from the following modules:
spring-boot-starter-web
spring-boot-starter-validation
spring-boot-starter-test
with Junit 5spring-boot-starter-data-jpa
<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>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. Testing if Application Bootstraps 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.
mvn test
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.howtodoinjava.employees.controllers.EmployeeController;
@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.
@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
.
@ExtendWith(MockitoExtension.class)
public class ServiceTests {
@InjectMocks
EmployeeService service;
@Mock
EmployeeRepository dao;
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
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
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.
@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. Integration Testing using TestRestTemplate
We can use TestRestTemplate
class to perform system testing. It helps in verifying the application as it looks to the client outside the application.
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class SystemTests {
@Autowired
private EmployeeController controller;
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testCreateReadDelete() {
String url = "http://localhost:"+port+"/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() {
String url = "http://localhost:"+port+"/wrong";
try {
restTemplate.getForEntity(url, String.class);
} catch (HttpClientErrorException e) {
Assertions.assertThat(e.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
}
}
8. Conclusion
This Spring boot 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 to your requirements.
Happy Learning !!
Comments