RESTful API Testing in Java with Mockito (Service Layer)

Aziz Kale
Javarevisited
Published in
8 min readOct 19, 2023

Mockito is a popular Java library used for creating and working with mock objects in unit testing. It allows you to simulate the behavior of real objects or dependencies (such as database access, external services, or complex classes) so that you can test your code in isolation. Mockito makes it easier to write unit tests by providing methods to create mock objects, define their behavior, and verify interactions with those mock objects during testing. It’s commonly used in combination with testing frameworks like JUnit to write effective and reliable unit tests for Java applications.

I will use the initial branch of a Maven-Spring Boot project that you can access at this link, for the implementation of Mockito and JUnit5, using IntelliJ IDEA as the IDE. You can download the project and follow the step-by-step instructions in the article to add the necessary code to your project.

I would like to begin by explaining some existing code in the project. If you prefer to dive straight into the implementation, you can follow this link.

The dependency below is added to the project.

<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

I have just the interface below for our EmployeeRepositoryclass.

package com.azizkale.mockitotutorial.dao;
import com.azizkale.mockitotutorial.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}

JpaRepository is part of the Spring Data JPA framework, which simplifies the implementation of data access layers in Java applications using the Java Persistence API (JPA). Here are some reasons why we use JpaRepository:

  1. CRUD Operations: JpaRepository provides out-of-the-box methods for common CRUD (Create, Read, Update, Delete) operations. We don't need to write boilerplate code for these operations; Spring Data JPA handles the implementation.
  2. Query Methods: We can define query methods in the repository interface by following naming conventions. Spring Data JPA automatically translates these method names into SQL queries, simplifying the process of creating custom queries.
  3. Paging and Sorting: JpaRepository supports pagination and sorting out of the box. Developers can easily retrieve a subset of data and control the order of results.
  4. Automatic Query Generation: Spring Data JPA can automatically generate queries based on method names, reducing the need for manual query writing. This is especially useful for standard queries like finding an entity by its ID or searching based on a specific field.
  5. Integration with Spring Framework: JpaRepository seamlessly integrates with the broader Spring ecosystem. This includes support for Spring's declarative transaction management, dependency injection, and other features.
  6. Consistent API: By using a consistent repository pattern, we can easily switch between different data sources (relational databases, NoSQL databases, etc.) without changing the higher-level business logic.

Explanations of Optional in the class EmployeeServiceImpl:

@Override
public Optional<Employee> findById(int id) throws EmployeeNotFoundException {
Optional<Employee> employee = employeeRepository.findById(id);
if(employee == null) {
throw new EmployeeNotFoundException("Employee not found with id :" + id);
}
return employee;
}

The Optional class is a container object that may or may not contain a non-null value. It is part of the java.util package in Java and is commonly used to represent the possibility of a value being absent. In the context of our code, Optional<Employee> is used in methods where a single employee is expected to be returned, such as in the findById method.

I have the class EmployeeDto in the project as well.

package com.azizkale.mockitotutorial.dto;
import java.sql.Date;

public class EmployeeDto {
private Integer id;
private String name;
private String department;
private String gender;
private Date dob;

//constructors, getters, setters
}

DTO (Data Transfer Object):

  • DTOs are used to transfer data between different layers of an application, such as between the business logic and the presentation layer, or between different microservices in a distributed system.
  • They serve as a container for data that needs to be transferred, encapsulating the fields and providing a way to transport this data without exposing the internal details of the domain objects.
  • DTOs play a crucial role in separating concerns by managing the representation of data entities within the domain model and facilitating the transfer of data between different layers of the application.
  • The structure of the DTO can be tailored to what is needed in a specific use case, rather than exposing all attributes of the domain object.
  • DTOs can help in controlling the information that is exposed outside of the application. They allow for fine-grained control over what data is sent to clients or other services.

And, I have the settings below to be able to use the h2-database.

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

What do mean annotations in the class Employee?

package com.azizkale.mockitotutorial.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Entity
@Table(name = "db_employee")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Column(name = "name")
private String name;

@Column(name = "department")
private String department;

@Column(name = "gender")
private String gender;

@Column(name = "dob")
private Date dob;
}

Entity: Marks the class as a JPA entity, indicating that instances of this class will be mapped to records in the database.

@Table(name = "db_employee"): Specifies the name of the database table to which this entity is mapped.

@Id: Marks the field as the primary key of the entity.

@GeneratedValue(strategy = GenerationType.IDENTITY): Configures the way the primary key will be generated. In our case, it indicates that the primary key is an auto-incremented identity column.

@Column(name = "name"), @Column(name = "department"), etc.: Specifies the mapping between the entity’s fields and the columns in the corresponding database table.

Lombok Annotations (@AllArgsConstructor, @NoArgsConstructor, @Builder, @Data): Lombok is a library that helps reduce boilerplate code in Java classes.

  • @AllArgsConstructor: Generates a constructor with all fields.
  • @NoArgsConstructor: Generates a default constructor with no arguments.
  • @Builder: Implements the builder pattern, providing a convenient way to construct instances of the class.
  • @Data: Combines various Lombok features, such as @ToString, @EqualsAndHashCode, and others. It generates getters, setters, toString, equals, and hashCode methods.
  • I should add this dependency to use Lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>

Testing Service Layer

Now, let’s test our methods in the class Service:

The Function create

In this section, I’ll use Mockito. So, I am adding the dependency Mockito.

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>

Now under the path below, I am creating the EmployeeServiceTest.

src/test/java/com/azizkale/mockitotutorial/service/EmployeeServiceTest:

package com.azizkale.mockitotutorial.service;

//imported packages (you can see these at the project in Github)

@ExtendWith(MockitoExtension.class)
public class EmployeeServiceTest {

// Creating a Mock to mimic EmployeeRepository.
@Mock
private EmployeeRepository employeeRepository;

// An instance of EmployeeServiceImpl, injected with the Mock created with @Mock.
@InjectMocks
private EmployeeServiceImpl employeeService;

// A test method to test the create method of the EmployeeService class.
@Test
public void EmployeeService_CreateEmployee_ReturnsEmployeeDto() {
// Creating an instance of Employee to be used for the test.
Employee employee = new Employee();

// Creating an instance of EmployeeDto to be used for the test.
EmployeeDto employeeDto = EmployeeDto.builder()
.name("Aziz Kale")
.department("IT")
.gender("male")
.dob(new Date())
.build();

// Using BeanUtils.copyProperties to copy properties from EmployeeDto to Employee for the test.
BeanUtils.copyProperties(employeeDto, employee);

// Mocking the employeeRepository.save method to return this instance of Employee whenever any instance of Employee is passed.
when(employeeRepository.save(Mockito.any(Employee.class))).thenReturn(employee);

// Calling the create method of EmployeeService and checking if it returns the expected instance of EmployeeDto.
EmployeeDto savedEmployee = employeeService.create(employeeDto);

// Using AssertJ to check the correctness of the expected values.
Assertions.assertThat(savedEmployee).isNotNull();
}
}

In this test class, a mock is used to mimic the behavior of the EmployeeRepository. The employeeRepository.save method is mocked to return a specific instance of Employee whenever any instance of Employee is passed. The EmployeeService's create method is then called, and AssertJ is used to check whether it returns the expected EmployeeDto.

Mock Annotation:

  • It is used to create a mock object of a class or interface. In our test class, we don’t want to use the actual implementation of EmployeeRepository because we are testing the EmployeeService class, not the repository.
  • By annotating a field with @Mock, Mockito creates a mock object for that class or interface. In our case, @Mock private EmployeeRepository employeeRepository; creates a mock of the EmployeeRepository class.

@InjectMocks Annotation:

  • It is used to automatically inject the mock objects annotated with @Mock into the instance of the class that is annotated with @InjectMocks.
  • In our test class, @InjectMocks private EmployeeServiceImpl employeeService; instructs Mockito to inject the mock EmployeeRepository into the EmployeeServiceImpl instance.

The Function findAll

@Test
public void EmployeeService_GetAllEmployee_ReturnsResponseDto(){
// Mocking a Page<Employee> using Mockito
Page<Employee> employees = Mockito.mock(Page.class);

// Configuring the behavior of the employeeRepository mock
// When findAll method of employeeRepository is called with any Pageable, it should return the mocked Page<Employee>.
when(employeeRepository.findAll(Mockito.any(Pageable.class))).thenReturn(employees);

// Calling the findAll method of employeeService with page number 1 and page size 10
EmployeeResponse employeeResponse = employeeService.findAll(1, 10);

// Asserting that the result (saveEmployee) is not null
Assertions.assertThat(employeeResponse).isNotNull();
}

1. Mocking Page<Employee>: Page<Employee> is typically used for paginated results. In this test, I created a mock instance of Page using Mockito. This is a way to simulate the behavior of the Page class without using a real instance.

2. Mocking Repository Behavior: Using Mockito’s when method, we configured the behavior of the employeeRepository mock. I specified that when the findAll method of employeeRepository is called with any Pageable argument, it should return the mocked Page<Employee>.

3. Calling the Service Method: Invoking the findAll method of the employeeService with page number 1 and page size 10. This is the method we are testing.

The Function findById

I want to test this function in two different situations.

1. When an Employee object exists.

@Test
public void EmployeeService_GetEmployeeById_WhenEmployeeExists_ReturnsEmployeeOptional() {
// Arrange
int employeeId = 1;
Employee mockEmployee = Mockito.mock(Employee.class); // create a mock Employee
when(employeeRepository.findById(employeeId)).thenReturn(Optional.of(mockEmployee));

// Act
Optional<Employee> result = employeeService.findById(employeeId);

// Assert
assertTrue(result.isPresent()); // assert that the result is present
assertSame(mockEmployee, result.get()); // assert that the result is the same instance as the mockEmployee
}

2. When an Employee object does not exist.

@Test
public void EmployeeService_GetEmployeeById_WhenEmployeeNotExist_ReturnsOptionalEmpty(){
// Arrange
int employeeId = 2;
when(employeeRepository.findById(employeeId)).thenReturn(Optional.empty());

// Act
Optional<Employee> result = employeeService.findById(employeeId);

// Assert
assertFalse(result.isPresent()); // assert that the result is not present
}

The function update

@Test
public void EmployeeService_UpdateEmployee_ReturnsEmployeeDto(){
int employeeId=1;
Employee employee = Employee.builder()
.id(employeeId).name("Aziz KAle")
.dob(new Date()).gender("Male")
.department("Tesing").build();

EmployeeDto employeeDto = employeeService.mapToDto(employee);
when(employeeRepository.findById(employeeId)).thenReturn(Optional.ofNullable(employee));
when(employeeRepository.save(employee)).thenReturn(employee);

EmployeeDto updatedEmployee = employeeService.update(employeeDto,employeeId);

Assertions.assertThat(updatedEmployee).isNotNull();
}

I checked the basic functionality of the update method. The test ensures that the method can handle the update operation, and the result is a non-null EmployeeDto. I used Mocking to isolate the test from the actual database operations and focus on the logic of the service.

The Function delete

@Test
public void EmployeeService_DeleteEmployee_ReturnsVoid() {
int employeeId = 1;
Employee employee = Employee.builder()
.id(employeeId).name("Aziz KAle")
.dob(new Date()).gender("Male")
.department("Tesing").build();

// When findById is invoked with the specified employeeId, it returns an Optional containing the employee.
when(employeeRepository.findById(employeeId)).thenReturn(Optional.ofNullable(employee));

// Configure the delete method to perform no action when called with an Employee object.
doNothing().when(employeeRepository).delete(employee);

// Invoke the delete method of the employeeService with the created employeeId.
employeeService.delete(employeeId);

// Use assertAll to ensure that no exceptions are thrown during the execution of the delete method.
assertAll(() -> employeeService.delete(employeeId));
}

Conclusion

We have tested the service layer of our Java-based RESTful API server. To ensure our tests don’t interfere with actual data, we utilized Mockito, a convenient tool for simulating behaviors. We meticulously named our test functions, not only for testing purposes but also to act as clear documentation for our API. This approach ensures that the API functions as anticipated, and our tests become a valuable guide for anyone using the project.

Thank you for taking the time to read my article.

--

--

Aziz Kale
Javarevisited

Highly Motivated, Passionate Full Stack Developer | EMM-IT Co. | Web: azizkale.com