In this comprehensive guide, we'll walk you through setting up a Spring Boot 3.2.5 application with a MySQL database using Docker Compose and writing unit tests for various layers of the application. We'll cover the following topics:
- Setting up the MySQL container using Docker Compose
- Configuring the Spring Boot application
- Defining the model, repository, service, and controller
- Writing unit tests for the repository layer
- Writing unit tests for the service layer
- Writing unit tests for the controller layer
- Writing integration tests
1. Setting up the MySQL container using Docker Compose
To start with, we'll set up a MySQL container using Docker Compose. This will allow us to run MySQL in a containerized environment, making it easy to manage and integrate with our Spring Boot application.
Create a docker-compose-mysql.yml file in your project root:
version: '2'
services:
### Mysql container
mysql:
image: mysql:latest
ports:
- "3306:3306"
volumes:
- /var/lib/mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: mypass
MYSQL_DATABASE: test_db
MYSQL_USER: test
MYSQL_PASSWORD: test_pass
MYSQL_ROOT_HOST: '%' # needs to be enclosed with quotes
Run the following command to start the MySQL container:
docker-compose -f docker-compose-mysql.yml up
2. Configuring the Spring Boot application
Next, configure the Spring Boot application to connect to the MySQL database. Add the following dependencies to your pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>demo-testing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-testing</name>
<description>Demo Spring Boot Unit Testing and Integration Testing</description>
<properties>
<java.version>21</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>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>runtime</scope>
</dependency>
<!--<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Update the application.yaml file to configure the MySQL connection:
spring:
application:
name: demo-testing
jpa:
show-sql: true
hibernate:
ddl-auto: update
datasource:
url: jdbc:mysql://localhost:3306/test_db?allowPublicKeyRetrieval=true
username: test
password: test_pass
driver-class-name: com.mysql.cj.jdbc.Driver
3. Defining the model, repository, service, and controller
Define an Employee model class:
package com.henry.demotesting.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(nullable = false)
private String email;
}
Define the EmployeeRepository interface:
package com.henry.demotesting.repository;
import com.henry.demotesting.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByEmail(String email);
@Query("select e from Employee e where e.firstName = ?1 and e.lastName = ?2")
Employee findByJPQL(String firstName, String lastName);
@Query("select e from Employee e where e.firstName =:firstName and e.lastName =:lastName")
Employee findByJPQLNameParams(@Param("firstName") String firstName, @Param("lastName") String lastName);
@Query(value = "select * from employees e where e.first_name = ?1 and e.last_name = ?2", nativeQuery = true)
Employee findByNativeSQL(String firstName, String lastName);
@Query(value = "select * from employees e where e.first_name =:firstName and e.last_name =:lastName", nativeQuery = true)
Employee findByNativeSQLWithNameParams(@Param("firstName") String firstName, @Param("lastName") String lastName);
}
Create the EmployeeService interface and its implementation:
package com.henry.demotesting.service;
import com.henry.demotesting.model.Employee;
import java.util.List;
import java.util.Optional;
public interface EmployeeService {
Employee saveEmployee(Employee employee);
List<Employee> getEmployees();
Optional<Employee> findById(Long id);
Employee updateEmployee(Employee employee);
void deleteEmployee(long id);
}
package com.henry.demotesting.service.impl;
import com.henry.demotesting.exception.ResourceNotFoundException;
import com.henry.demotesting.model.Employee;
import com.henry.demotesting.repository.EmployeeRepository;
import com.henry.demotesting.service.EmployeeService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class EmployeeServiceImpl implements EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeServiceImpl(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
public Employee saveEmployee(Employee employee) {
Optional<Employee> savedEmployee = employeeRepository.findByEmail(employee.getEmail());
if(savedEmployee.isPresent()){
throw new ResourceNotFoundException("Employee already exist with given email: "+employee.getEmail());
}
return employeeRepository.save(employee);
}
@Override
public List<Employee> getEmployees() {
return employeeRepository.findAll();
}
@Override
public Optional<Employee> findById(Long id) {
return employeeRepository.findById(id);
}
@Override
public Employee updateEmployee(Employee employee) {
return employeeRepository.save(employee);
}
@Override
public void deleteEmployee(long id) {
employeeRepository.deleteById(id);
}
}
Create the EmployeeController class:
package com.henry.demotesting.controller;
import com.henry.demotesting.model.Employee;
import com.henry.demotesting.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Employee createEmployee(@RequestBody Employee employee){
return employeeService.saveEmployee(employee);
}
@GetMapping
public List<Employee> getAllEmployees(){
return employeeService.getEmployees();
}
@GetMapping("/{id}")
public ResponseEntity<Employee> findEmployeeId(@PathVariable Long id){
return employeeService.findById(id)
.map(ResponseEntity::ok)
.orElseGet(()-> ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable long id,
@RequestBody Employee employee){
return employeeService.findById(id)
.map(savedEmployee -> {
savedEmployee.setFirstName(employee.getFirstName());
savedEmployee.setLastName(employee.getLastName());
savedEmployee.setEmail(employee.getEmail());
var updEmployee = employeeService.updateEmployee(savedEmployee);
return new ResponseEntity<>(updEmployee, HttpStatus.OK);
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteEmployee(@PathVariable long id){
employeeService.deleteEmployee(id);
return new ResponseEntity<String>("Employee deleted successfully!", HttpStatus.OK);
}
}
4. Writing unit tests for the repository layer
Create a test class for EmployeeRepository:
package com.henry.demotesting.repository;
import com.henry.demotesting.model.Employee;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.Optional;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
@DataJpaTest
public class EmployeeRepositoryTests {
@Autowired
private EmployeeRepository employeeRepository;
//JUnit test for save employee operation
@DisplayName("JUnit test for save employee operation")
@Test
public void givenEmployeeObject_whenSave_thenReturnSavedEmployee(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
//when - action or the behaviour that we are going test
var savedEmployee = employeeRepository.save(employee);
// then - verify the output
assertThat(savedEmployee).isNotNull();
assertThat(savedEmployee.getId()).isGreaterThan(0);
}
//JUnit test for get all employees operation
@DisplayName("JUnit test for get all employees operation")
@Test
public void givenEmployeeList_whenFindAll_thenEmployeeList(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
var employee1 = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
employeeRepository.save(employee1);
//when - action or the behaviour that we are going test
var employeeList = employeeRepository.findAll();
// then - verify the output
assertThat(employeeList).isNotNull();
assertThat(employeeList.size()).isEqualTo(2);
}
//JUnit test for get employee id operation
@DisplayName("JUnit test for get employee id operation")
@Test
public void givenEmployeeObject_whenFindById_thenReturnEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
var employeeDB = employeeRepository.findById(employee.getId()).get();
// then - verify the output
assertThat(employeeDB).isNotNull();
}
//JUnit test for get employee email operation
@DisplayName("JUnit test for get employee email operation")
@Test
public void givenEmployeeObject_whenFindByEmail_thenReturnEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
var employeeDB = employeeRepository.findByEmail(employee.getEmail()).get();
// then - verify the output
assertThat(employeeDB).isNotNull();
}
//JUnit test for update employee operation
@DisplayName("JUnit test for update employee operation")
@Test
public void givenEmployeeObject_whenUpdateEmployee_thenReturnUpdateEmployee(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
var savedEmployee = employeeRepository.findById(employee.getId()).get();
savedEmployee.setEmail("henry@gmail.com");
savedEmployee.setFirstName("henry2");
var updateEmployee = employeeRepository.save(savedEmployee);
// then - verify the output
assertThat(updateEmployee.getEmail()).isEqualTo("henry@gmail.com");
assertThat(updateEmployee.getFirstName()).isEqualTo("henry2");
}
//JUnit test for delete employee operation
@DisplayName("JUnit test for delete employee operation")
@Test
public void givenEmployeeObject_whenDelete_thenRemoveEmployee(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
employeeRepository.delete(employee);
Optional<Employee> employeeOptional = employeeRepository.findById(employee.getId());
// then - verify the output
assertThat(employeeOptional).isEmpty();
}
//JUnit test for custom query using JPQL with index
@DisplayName("JUnit test for custom query using JPQL with index")
@Test
public void givenFirstNameAndLastName_whenFindByJPQL_thenEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
var firstName = "Henry";
var lastName = "x";
//when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByJPQL(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
//JUnit test for custom query using JPQL with nameParams
@DisplayName("JUnit test for custom query using JPQL with nameParams")
@Test
public void givenFirstNameAndLastName_whenFindByJPQLNamedParams_thenEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
var firstName = "Henry";
var lastName = "x";
//when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByJPQLNameParams(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
//JUnit test for using Nativa SQL with index params
@DisplayName("JUnit test for using Nativa SQL with index params")
@Test
public void givenFirstNameAndLastName_whenFindByNativeSQL_thenEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
var firstName = "Henry";
var lastName = "x";
//when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByNativeSQL(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
//JUnit test for using Nativa SQL with named params
@DisplayName("JUnit test for using Nativa SQL with named params")
@Test
public void givenFirstNameAndLastName_whenFindByNativeSQLWithParams_thenEmployeeObject(){
//given - precondition or setup
var employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
employeeRepository.save(employee);
var firstName = "Henry";
var lastName = "x";
//when - action or the behaviour that we are going test
Employee savedEmployee = employeeRepository.findByNativeSQLWithNameParams(firstName, lastName);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
}
5. Writing unit tests for the service layer
Create a test class for EmployeeService:
package com.henry.demotesting.service;
import com.henry.demotesting.exception.ResourceNotFoundException;
import com.henry.demotesting.model.Employee;
import com.henry.demotesting.repository.EmployeeRepository;
import com.henry.demotesting.service.impl.EmployeeServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.Mockito.*;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ExtendWith(MockitoExtension.class)
public class EmployeeServiceTests {
@Mock
private EmployeeRepository employeeRepository;
@InjectMocks
private EmployeeServiceImpl employeeService;
private Employee employee;
@BeforeEach
public void setup(){
/***
* employeeRepository = Mockito.mock(EmployeeRepository.class);
employeeService = new EmployeeServiceImpl(employeeRepository);
**/
employee = Employee.builder()
.id(1L)
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
}
//JUnit test for savedEmployee method
@DisplayName("JUnit test for savedEmployee method")
@Test
public void givenEmployeeObject_whenSavedEmployee_thenReturnEmployeeObject(){
//given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.empty());
given(employeeRepository.save(employee)).willReturn(employee);
//when - action or the behaviour that we are going test
Employee savedEmployee = employeeService.saveEmployee(employee);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
//JUnit test for savedEmployee method which throws exception
@DisplayName("JUnit test for savedEmployee method which throws exception")
@Test
public void givenExistingEmail_whenSavedEmployee_thenThrowsException(){
//given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.of(employee));
//when - action or the behaviour that we are going test
org.junit.jupiter.api.Assertions.assertThrows(ResourceNotFoundException.class, () -> {
employeeService.saveEmployee(employee);
});
// then - verify the output
verify(employeeRepository, never()).save(any(Employee.class));
}
//JUnit test for getAllEmployees method
@DisplayName("JUnit test for getAllEmployees method")
@Test
public void givenEmployeesList_whenGetAllEmployees_thenReturnEmployeesList(){
//given - precondition or setup
var employee1 = Employee.builder()
.id(2L)
.firstName("Henry1")
.lastName("x1")
.email("test1@gmail.com")
.build();
given(employeeRepository.findAll()).willReturn(List.of(employee, employee1));
//when - action or the behaviour that we are going test
var employeeList = employeeService.getEmployees();
// then - verify the output
assertThat(employeeList).isNotNull();
assertThat(employeeList.size()).isEqualTo(2);
}
//JUnit test for getAllEmployees method
@DisplayName("JUnit test for getAllEmployees method (negative scenario)")
@Test
public void givenEmptyEmployeesList_whenGetAllEmployees_thenReturnEmptyEmployeesList(){
//given - precondition or setup
var employee1 = Employee.builder()
.id(2L)
.firstName("Henry1")
.lastName("x1")
.email("test1@gmail.com")
.build();
given(employeeRepository.findAll()).willReturn(Collections.emptyList());
//when - action or the behaviour that we are going test
var employeeList = employeeService.getEmployees();
// then - verify the output
assertThat(employeeList).isEmpty();
assertThat(employeeList.size()).isEqualTo(0);
}
//JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method")
@Test
public void givenEmployeeId_whenGetEmployeeId_thenEmployeeObject(){
//given - precondition or setup
given(employeeRepository.findById(employee.getId())).willReturn(Optional.of(employee));
//when - action or the behaviour that we are going test
var employeeObject = employeeService.findById(employee.getId());
// then - verify the output
assertThat(employeeObject).isNotEmpty();
}
//JUnit test for updateEmployee method
@DisplayName("JUnit test for updateEmployee method")
@Test
public void givenEmployeeObject_whenUpdateEmployee_thenReturnUpdateEmployee(){
//given - precondition or setup
given(employeeRepository.save(employee)).willReturn(employee);
employee.setEmail("henry2@test.com");
employee.setFirstName("test2");
employee.setLastName("x3");
//when - action or the behaviour that we are going test
var updateEmployee = employeeService.updateEmployee(employee);
// then - verify the output
assertThat(updateEmployee.getEmail()).isEqualTo("henry2@test.com");
assertThat(updateEmployee.getFirstName()).isEqualTo("test2");
assertThat(updateEmployee.getLastName()).isEqualTo("x3");
}
//JUnit test for deleteEmployee method
@DisplayName("JUnit test for deleteEmployee method")
@Test
public void givenEmployeeObject_whenDeleteEmployee_thenReturnDeleteObject(){
long employeeId = 1L;
//given - precondition or setup
willDoNothing().given(employeeRepository).deleteById(employeeId);
//when - action or the behaviour that we are going test
employeeService.deleteEmployee(employeeId);
// then - verify the output
verify(employeeRepository, times(1)).deleteById(employeeId);
}
}
6. Writing unit tests for the controller layer
Create a test class for EmployeeController:
package com.henry.demotesting.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.henry.demotesting.model.Employee;
import com.henry.demotesting.service.EmployeeService;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest
public class EmployeeControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private EmployeeService employeeService;
@Autowired
private ObjectMapper objectMapper;
private Employee employee;
@BeforeEach
public void setup(){
employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
}
//JUnit test for createEmployee
@DisplayName("JUnit test for create Employee")
@Test
public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {
//given - precondition or setup
given(employeeService.saveEmployee(ArgumentMatchers.any(Employee.class)))
.willAnswer(invocation -> invocation.getArgument(0));
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(post("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee))
);
// then - verify the output
response.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee.getEmail())));
}
//JUnit test for getAllEmployees method
@DisplayName("Junit test for getAllEmployees method")
@Test
public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() throws Exception {
var employee1 = Employee.builder()
.firstName("Henry1")
.lastName("x1")
.email("test1@gmail.com")
.build();
//given - precondition or setup
List<Employee> listOfEmployees = new ArrayList<>();
listOfEmployees.add(employee);
listOfEmployees.add(employee1);
given(employeeService.getEmployees()).willReturn(listOfEmployees);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees"));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.size()"
, CoreMatchers.is(listOfEmployees.size())));
}
//JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method (positive scenario)")
@Test
public void givenEmployeeId_whenGetEmployeeId_thenReturnEmployeeObject() throws Exception {
long employeeId = 1L;
//given - precondition or setup
given(employeeService.findById(employeeId)).willReturn(Optional.of(employee));
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employeeId));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee.getEmail())));
}
//JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method (negative scenario)")
@Test
public void givenEmployeeId_whenGetEmployeeId_thenReturnNegativeScenarioEmployeeObject() throws Exception {
long employeeId = 1L;
//given - precondition or setup
given(employeeService.findById(employeeId)).willReturn(Optional.empty());
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employeeId));
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
//JUnit test for update employee REST API
@DisplayName("JUnit test for update employee REST API (Positive scenario)")
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnEmployeeObject() throws Exception {
//given - precondition or setup
long id = 1L;
given(employeeService.findById(id)).willReturn(Optional.of(employee));
given(employeeService.updateEmployee(ArgumentMatchers.any(Employee.class)))
.willAnswer(invocation -> invocation.getArgument(0));
employee.setEmail("henry2@test.com");
employee.setFirstName("test2");
employee.setLastName("x3");
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee))
);
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee.getEmail())));
}
//JUnit test for update employee REST API
@DisplayName("JUnit test for update employee REST API (Negative scenario)")
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnNegativeScenarioEmployee() throws Exception {
//given - precondition or setup
long id = 1L;
given(employeeService.findById(id)).willReturn(Optional.empty());
given(employeeService.updateEmployee(ArgumentMatchers.any(Employee.class)))
.willAnswer(invocation -> invocation.getArgument(0));
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee))
);
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
//JUnit test for deleteEmployee
@DisplayName("JUnit test for deleteEmployee")
@Test
public void givenEmployeeId_whenDeleteEmployee_thenReturn200() throws Exception {
long id = 1L;
//given - precondition or setup
willDoNothing().given(employeeService).deleteEmployee(id);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(delete("/api/employees/{id}", id));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print());
}
}
7. Writing integration tests
Create a test class for integration testing:
package com.henry.demotesting.integration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.henry.demotesting.model.Employee;
import com.henry.demotesting.repository.EmployeeRepository;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class EmployeeControllerITests {
@Autowired
private MockMvc mockMvc;
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private ObjectMapper objectMapper;
private Employee employee;
@BeforeEach
void setup(){
employeeRepository.deleteAll();
employee = Employee.builder()
.firstName("Henry")
.lastName("x")
.email("test@gmail.com")
.build();
}
//JUnit test for createEmployee
@DisplayName("JUnit test for create Employee")
@Test
public void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {
//given - precondition or setup
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(post("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee))
);
// then - verify the output
response.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee.getEmail())));
}
//JUnit test for getAllEmployees method
@DisplayName("Junit test for getAllEmployees method")
@Test
public void givenListOfEmployees_whenGetAllEmployees_thenReturnEmployeesList() throws Exception {
var employee1 = Employee.builder()
.firstName("Henry1")
.lastName("x1")
.email("test1@gmail.com")
.build();
//given - precondition or setup
List<Employee> listOfEmployees = new ArrayList<>();
listOfEmployees.add(employee);
listOfEmployees.add(employee1);
employeeRepository.saveAll(listOfEmployees);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees"));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.size()"
, CoreMatchers.is(listOfEmployees.size())));
}
//JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method (positive scenario)")
@Test
public void givenEmployeeId_whenGetEmployeeId_thenReturnEmployeeObject() throws Exception {
//given - precondition or setup
var employee1 = employeeRepository.save(employee);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employee1.getId()));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee.getEmail())));
}
//JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method (negative scenario)")
@Test
public void givenEmployeeId_whenGetEmployeeId_thenReturnNegativeScenarioEmployeeObject() throws Exception {
long employeeId = 1L;
//given - precondition or setup
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employeeId));
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
//JUnit test for update employee REST API
@DisplayName("JUnit test for update employee REST API (Positive scenario)")
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnEmployeeObject() throws Exception {
//given - precondition or setup
Employee employee1 = employeeRepository.save(employee);
employee1.setEmail("henry2@test.com");
employee1.setFirstName("test2");
employee1.setLastName("x3");
employeeRepository.save(employee1);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", employee1.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee1))
);
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", CoreMatchers.is(employee1.getFirstName())))
.andExpect(jsonPath("$.lastName", CoreMatchers.is(employee1.getLastName())))
.andExpect(jsonPath("$.email", CoreMatchers.is(employee1.getEmail())));
}
//JUnit test for update employee REST API
@DisplayName("JUnit test for update employee REST API (Negative scenario)")
@Test
public void givenUpdatedEmployee_whenUpdateEmployee_thenReturnNegativeScenarioEmployee() throws Exception {
//given - precondition or setup
long id = 1L;
employeeRepository.save(employee);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(put("/api/employees/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee))
);
// then - verify the output
response.andExpect(status().isNotFound())
.andDo(print());
}
//JUnit test for deleteEmployee
@DisplayName("JUnit test for deleteEmployee")
@Test
public void givenEmployeeId_whenDeleteEmployee_thenReturn200() throws Exception {
//given - precondition or setup
var employee1 = employeeRepository.save(employee);
//when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(delete("/api/employees/{id}", employee1.getId()));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print());
}
}
Conclusion
In this comprehensive guide, we've covered setting up a Spring Boot 3.2.5 application with a MySQL database using Docker Compose, and writing unit tests for the repository, service, and controller layers, as well as integration tests. This setup allows you to ensure that your application functions correctly across different layers and provides a solid foundation for further development and testing.