Spring boot Unit testing for repositories, controllers and services using JUnit 5 and Mockito.

In this tutorial, we will learn how to write unit tests for the Spring Boot application using Junit 5 and Mockito.

Spring Boot provides different testing approaches, such as Unit Testing, Integration Testing, and End-to-End Testing. In this tutorial, we will learn how to write unit tests for the Spring Boot application.

Zoom image will be displayed

Prerequisites

  1. Good Knowledge of Java.
  2. Good Understanding of Spring Boot.
  3. Good Understanding of MySQL workbench and IDE (In this project I have used Intellij IDE).

Required Technologies and Apps

  1. Java ( latest JDK 17 or above )
  2. Spring Boot project file.
  3. Intellij IDE
  4. Postman
  5. MySQL Workbench

Download Source Code from Github   — Download Source Code

What is Unit Testing ?

Unit testing is a software testing technique where individual units or components of a software application are tested in isolation to verify that they perform as expected.

Spring Boot Starter Test Dependency

The Spring Boot Starter Test dependency provides essential libraries and utilities for testing Spring Boot applications, including JUnit, Hamcrest, Mockito, and AssertJ. It simplifies the process of writing and executing tests for Spring Boot applications.

What is JUnit ?

JUnit is a popular open-source testing framework for Java. It provides annotations to define test methods, test classes, and test suites, along with assertions for verifying expected outcomes. JUnit makes it easy to write and execute unit tests, ensuring the correctness of Java code.

What is AssertJ Library?

AssertJ is a popular Java library that provides fluent, expressive assertions for writing clearer and more readable unit tests. And also It provides helpful error messages.

Set up the Projet

First, we need to make crud application.

3. Make a project file with the spring initializer tool.

Spring Initializer Tool

Download the project and Open Project from IntelliJ.

2. Make the project structure.

Project Structure

3. Add class and interfaces.

Application.Properties File

spring.datasource.url=jdbc:mysql://localhost:3306/unit_test_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=Online12@
spring.jpa.hibernate.ddl-auto = update

Employee Entity class

package test.example.springboot.test.demo.Model;
import jakarta.persistence.*;
import lombok.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "employees")
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(nullable = false)
private String firstName;

@Column(nullable = false)
private String lastName;

@Column(nullable = false)
private String email;

}

Employee Repository

package test.example.springboot.test.demo.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import test.example.springboot.test.demo.Model.Employee;
import java.util.Optional;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByEmail(String email);
}

Employee Controller

package test.example.springboot.test.demo.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import test.example.springboot.test.demo.Model.Employee;
import test.example.springboot.test.demo.Service.EmployeeService;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/employees")
public class EmployeeController {


private 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.getAllEmployees();
}

@GetMapping("{id}")
public ResponseEntity<Optional<Employee>> getEmployeeById(@PathVariable("id") long id){
return new ResponseEntity<Optional<Employee>>(employeeService.getEmployeeById(id),HttpStatus.OK);
}

@PutMapping("{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable("id") long id,
@RequestBody Employee employee)
{
return new ResponseEntity<Employee>(employeeService.updateEmployee(employee,id),HttpStatus.OK);
}

@DeleteMapping("{id}")
public ResponseEntity<String> deleteEmployee(@PathVariable("id") long id){
employeeService.deleteEmployee(id);
return new ResponseEntity<String>("Employee deleted successfully!.", HttpStatus.OK);

}
}

Employee Service

package test.example.springboot.test.demo.Service;
import test.example.springboot.test.demo.Model.Employee;
import java.util.List;
import java.util.Optional;

public interface EmployeeService {
Employee saveEmployee(Employee employee);
List<Employee> getAllEmployees();
Optional<Employee> getEmployeeById(long id);
Employee updateEmployee(Employee employee,long id);
void deleteEmployee(long id);
}

Service Implementation

package test.example.springboot.test.demo.Service.Impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import test.example.springboot.test.demo.Model.Employee;
import test.example.springboot.test.demo.Repository.EmployeeRepository;
import test.example.springboot.test.demo.Service.EmployeeService;
import java.util.List;
import java.util.Optional;

@Service
public class EmployeeServiceImpl implements EmployeeService {


private 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 RuntimeException("Employee already exist with given email:" + employee.getEmail());
}
return employeeRepository.save(employee);
}

@Override
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}

@Override
public Optional<Employee> getEmployeeById(long id) {
return employeeRepository.findById(id);
}

@Override
public Employee updateEmployee(Employee employee,long id) {
Employee existingEmployee = employeeRepository.findById(id)
.orElseThrow(()->new RuntimeException());

existingEmployee.setFirstName(employee.getFirstName());
existingEmployee.setLastName(employee.getLastName());
existingEmployee.setEmail(employee.getEmail());

employeeRepository.save(existingEmployee);
return existingEmployee;
}

@Override
public void deleteEmployee(long id) {
employeeRepository.deleteById(id);
}
}

4. Test application Using Postman

Test Using Postman
MySQL Work Bench

This way, you can test all your APIs. And also, I saved two employees.

Let’s write the unit test for the repository layer with JUnit (Don’t need Mockito)

Test Package Structure

Open EmployeeRepositoryUnitTest.java class.

package test.example.springboot.test.demo.Repository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;
import test.example.springboot.test.demo.Model.Employee;
import java.util.List;
import java.util.Optional;

@DataJpaTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EmployeeRepositoryUnitTests {

@Autowired
private EmployeeRepository employeeRepository;

@Test
@DisplayName("Test 1:Save Employee Test")
@Order(1)
@Rollback(value = false)
public void saveEmployeeTest(){

//Action
Employee employee = Employee.builder()
.firstName("Sam")
.lastName("Curran")
.email("sam@gmail.com")
.build();

employeeRepository.save(employee);

//Verify
System.out.println(employee);
Assertions.assertThat(employee.getId()).isGreaterThan(0);
}

@Test
@Order(2)
public void getEmployeeTest(){

//Action
Employee employee = employeeRepository.findById(1L).get();
//Verify
System.out.println(employee);
Assertions.assertThat(employee.getId()).isEqualTo(1L);
}

@Test
@Order(3)
public void getListOfEmployeesTest(){
//Action
List<Employee> employees = employeeRepository.findAll();
//Verify
System.out.println(employees);
Assertions.assertThat(employees.size()).isGreaterThan(0);

}

@Test
@Order(4)
@Rollback(value = false)
public void updateEmployeeTest(){

//Action
Employee employee = employeeRepository.findById(1L).get();
employee.setEmail("samcurran@gmail.com");
Employee employeeUpdated = employeeRepository.save(employee);

//Verify
System.out.println(employeeUpdated);
Assertions.assertThat(employeeUpdated.getEmail()).isEqualTo("samcurran@gmail.com");

}

@Test
@Order(5)
@Rollback(value = false)
public void deleteEmployeeTest(){
//Action
employeeRepository.deleteById(1L);
Optional<Employee> employeeOptional = employeeRepository.findById(1L);

//Verify
Assertions.assertThat(employeeOptional).isEmpty();
}

}

@TestMethodOrder(MethodOrderer.OrderAnnotation.class) specify the order of test execution based on the order specified by the @Order annotation.

Note:

You can use MySQL or any other database for your project to save the actual data. If you use the same database for testing, it will affect your actual data. So, you can use the in-memory H2 database for testing. It is the common way.

Add H2 dependency to pom.xml file.

 <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

Change the application.properties file.

#MysQL Database Configuration for Project
spring.datasource.url=jdbc:mysql://localhost:3306/unit_test_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=Online12@
spring.jpa.hibernate.ddl-auto = update

#H2 Database Configuration for testing
spring.datasource.test.url=jdbc:h2:mem/unit_test_db
spring.datasource.test.diver-class-name=org.h2.Driver
spring.datasource.test.username=sa
spring.datasource.test.password=password
spring.jpa.test.hibernate.ddl-auto = create-drop

Run the EmployeeRepositoryUnitTest.java class.

Let’s write the unit test for controller layer with JUnit 5 and Mockito

What is Mockito?

Mockito is a popular Java framework used for mocking objects in unit tests. Mockito can be used in unit tests to mock dependencies and isolate the code being tested.
Controllers often depend on service layer components to perform business logic or interact with the application’s data layer (e.g., repositories). Mock the service layer components using Mockito to isolate the controller being tested from the actual service layer implementation.

What is MockMvc?

MockMvc is not part of Mockito. MockMvc is a class provided by the Spring Test framework specifically for testing Spring MVC controllers.

Open EmployeeControllerUnitTests class

package test.example.springboot.test.demo.Controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.springframework.http.MediaType;
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.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import test.example.springboot.test.demo.Model.Employee;
import test.example.springboot.test.demo.Service.EmployeeService;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.mockito.BDDMockito.*;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
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(EmployeeController.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EmployeeControllerUnitTests {

@Autowired
private MockMvc mockMvc;

@MockBean
private EmployeeService employeeService;

@Autowired
private ObjectMapper objectMapper;

Employee employee;

@BeforeEach
public void setup(){

employee = Employee.builder()
.id(1L)
.firstName("John")
.lastName("Cena")
.email("john@gmail.com")
.build();

}

//Post Controller
@Test
@Order(1)
public void saveEmployeeTest() throws Exception{
// precondition
given(employeeService.saveEmployee(any(Employee.class))).willReturn(employee);

// action
ResultActions response = mockMvc.perform(post("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee)));

// verify
response.andDo(print()).
andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName",
is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName",
is(employee.getLastName())))
.andExpect(jsonPath("$.email",
is(employee.getEmail())));
}

//Get Controller
@Test
@Order(2)
public void getEmployeeTest() throws Exception{
// precondition
List<Employee> employeesList = new ArrayList<>();
employeesList.add(employee);
employeesList.add(Employee.builder().id(2L).firstName("Sam").lastName("Curran").email("sam@gmail.com").build());
given(employeeService.getAllEmployees()).willReturn(employeesList);

// action
ResultActions response = mockMvc.perform(get("/api/employees"));

// verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.size()",
is(employeesList.size())));

}

//get by Id controller
@Test
@Order(3)
public void getByIdEmployeeTest() throws Exception{
// precondition
given(employeeService.getEmployeeById(employee.getId())).willReturn(Optional.of(employee));

// action
ResultActions response = mockMvc.perform(get("/api/employees/{id}", employee.getId()));

// verify
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", is(employee.getFirstName())))
.andExpect(jsonPath("$.lastName", is(employee.getLastName())))
.andExpect(jsonPath("$.email", is(employee.getEmail())));

}


//Update employee
@Test
@Order(4)
public void updateEmployeeTest() throws Exception{
// precondition
given(employeeService.getEmployeeById(employee.getId())).willReturn(Optional.of(employee));
employee.setFirstName("Max");
employee.setEmail("max@gmail.com");
given(employeeService.updateEmployee(employee,employee.getId())).willReturn(employee);

// action
ResultActions response = mockMvc.perform(put("/api/employees/{id}", employee.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee)));

// verify
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", is(employee.getFirstName())))
.andExpect(jsonPath("$.email", is(employee.getEmail())));
}


// delete employee
@Test
public void deleteEmployeeTest() throws Exception{
// precondition
willDoNothing().given(employeeService).deleteEmployee(employee.getId());

// action
ResultActions response = mockMvc.perform(delete("/api/employees/{id}", employee.getId()));

// then - verify the output
response.andExpect(status().isOk())
.andDo(print());
}
}
Zoom image will be displayed

ObjectMapper class is used for converting Java objects to JSON and vice versa.

given: This method is part of the Mockito framework. It is used to specify a precondition or setup for the test.

Run the test class.

Zoom image will be displayed

Let’s write the unit test for service layer with JUnit and Mockito

Open EmployeeServiceUnitTests class

package test.example.springboot.test.demo.Service;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import test.example.springboot.test.demo.Model.Employee;
import test.example.springboot.test.demo.Repository.EmployeeRepository;
import test.example.springboot.test.demo.Service.Impl.EmployeeServiceImpl;

import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;


@ExtendWith(MockitoExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EmployeeServiceUnitTests {

@Mock
private EmployeeRepository employeeRepository;

@InjectMocks
private EmployeeServiceImpl employeeService;

private Employee employee;


@BeforeEach
public void setup(){

employee = Employee.builder()
.id(1L)
.firstName("John")
.lastName("Cena")
.email("john@gmail.com")
.build();

}

@Test
@Order(1)
public void saveEmployeeTest(){
// precondition
given(employeeRepository.save(employee)).willReturn(employee);

//action
Employee savedEmployee = employeeService.saveEmployee(employee);

// verify the output
System.out.println(savedEmployee);
assertThat(savedEmployee).isNotNull();
}

@Test
@Order(2)
public void getEmployeeById(){
// precondition
given(employeeRepository.findById(1L)).willReturn(Optional.of(employee));

// action
Employee existingEmployee = employeeService.getEmployeeById(employee.getId()).get();

// verify
System.out.println(existingEmployee);
assertThat(existingEmployee).isNotNull();

}


@Test
@Order(3)
public void getAllEmployee(){
Employee employee1 = Employee.builder()
.id(2L)
.firstName("Sam")
.lastName("Curran")
.email("sam@gmail.com")
.build();

// precondition
given(employeeRepository.findAll()).willReturn(List.of(employee,employee1));

// action
List<Employee> employeeList = employeeService.getAllEmployees();

// verify
System.out.println(employeeList);
assertThat(employeeList).isNotNull();
assertThat(employeeList.size()).isGreaterThan(1);
}

@Test
@Order(4)
public void updateEmployee(){

// precondition
given(employeeRepository.findById(employee.getId())).willReturn(Optional.of(employee));
employee.setEmail("max@gmail.com");
employee.setFirstName("Max");
given(employeeRepository.save(employee)).willReturn(employee);

// action
Employee updatedEmployee = employeeService.updateEmployee(employee,employee.getId());

// verify
System.out.println(updatedEmployee);
assertThat(updatedEmployee.getEmail()).isEqualTo("max@gmail.com");
assertThat(updatedEmployee.getFirstName()).isEqualTo("Max");
}

@Test
@Order(5)
public void deleteEmployee(){

// precondition
willDoNothing().given(employeeRepository).deleteById(employee.getId());

// action
employeeService.deleteEmployee(employee.getId());

// verify
verify(employeeRepository, times(1)).deleteById(employee.getId());
}


}
Zoom image will be displayed

Run the class

Zoom image will be displayed

Summary

In this tutorial we have written Spring Boot unit testing for Repositories, Controllers and Services using JUnit 5 and Mockito.

Integration Testing

Many experienced programmers and teams prioritize both Unit and Integration testing to ensure robust, reliable, and maintainable applications.

If you like, you can learn integration testing. My Integration Testing artical —https://medium.com/@Lakshitha_Fernando/integration-testing-bb31676915ae

Post a Comment

Previous Post Next Post