在上一篇教程中,我们看到了Spring Boot Unit使用JUnit和Mockito测试CRUD REST API。在本教程中,我们将学习如何使用JUnit5和Mockito框架执行单元测试Spring引导服务层单元测试。
为了测试服务层组件,我们必须使用Mockito框架模拟存储库层组件。我们不必使用数据库进行单元测试。
查看我的Spring boot测试Udemy课程:****Testing Spring Boot Application with JUnit and Mockito (Includes Testcontainers)
Spring boot为Spring boot应用程序的单元测试和集成测试提供了Spring boot-starter测试依赖性:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
SpringBootStarter测试依赖项是测试SpringBoot应用程序的主要依赖项。它包含测试所需的所有必要元素。
对于单元测试服务层,我们将使用以下测试库:
它实际上是Java的标准测试框架。
JUnit的当前版本是5+。JUnit5的主要目标是支持Java8及以上版本,并支持多种不同的测试风格。
Mockito是一个模拟框架。它是一个基于Java的库,用于创建简单的基本测试API,以执行Java应用程序的单元测试。
使用Mockito框架的主要目的是通过模拟外部依赖项并在测试代码中使用它们来简化测试的开发。
在https://site.mockito.org/了解Mockito框架
AssertJ是一个Java库,它提供了一组丰富的断言和真正有用的错误消息,提高了测试代码的可读性,并且设计为在您喜爱的IDE中非常容易使用。
Spring boot starter测试依赖项在内部提供了assertj核心依赖项,因此我们不必在Spring启动项目中手动添加assertj-core依赖项。
在https://assertj.github.io/doc/阅读有关AssertJ的更多信息
使用spring initialize创建Spring Boot项目并添加以下依赖项:
将Spring引导项目生成为zip文件,将其解压缩并导入IntelliJIDEA。
确保已在spring boot项目中添加以下依赖项:
<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.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>
接下来,让我们创建一个包含以下内容的Employee JPA实体:
import lombok.*;
import javax.persistence.*;
@Setter
@Getter
@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;
}
注意,我们使用Lombok注解来减少样板代码。
@Entity注解用于将类标记为持久Java类。
@Table注解用于提供该实体将映射到的表的详细信息。
@Id注解用于定义主键。
@GeneratedValue注解用于定义主键生成策略。在上面的例子中,我们已经将主键声明为自动增量字段。
@Column注解用于定义将映射到注解字段的列的属性。您可以定义几个属性,如名称、长度、可为空、可更新等。
让我们创建扩展JpaRepository接口的EmployeeRepository:
import net.javaguides.springboot.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
让我们用CRUD方法创建EmployeeService接口:
import net.javaguides.springboot.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 updatedEmployee);
void deleteEmployee(long id);
}
让我们创建一个实现EmployeeService接口的EmployereServiceImpl类:
import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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 ResourceNotFoundException("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 updatedEmployee) {
return employeeRepository.save(updatedEmployee);
}
@Override
public void deleteEmployee(long id) {
employeeRepository.deleteById(id);
}
}
让我们开始为EmployeeService编写单元测试。我们应该能够在不使用任何Spring特性的情况下为EmployeeService编写单元测试。
我们将使用@mock创建EmployeeRepository
的mock,并使用mock EmployeeRepository示例创建一个EmployeeServiceImpl
示例。
import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.impl.EmployeeServiceImpl;
import static org.assertj.core.api.Assertions.assertThat;
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.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("Ramesh")
.lastName("Fadatare")
.email("ramesh@gmail.com")
.build();
}
// JUnit test for saveEmployee method
@DisplayName("JUnit test for saveEmployee method")
@Test
public void givenEmployeeObject_whenSaveEmployee_thenReturnEmployeeObject(){
// given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.empty());
given(employeeRepository.save(employee)).willReturn(employee);
System.out.println(employeeRepository);
System.out.println(employeeService);
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeService.saveEmployee(employee);
System.out.println(savedEmployee);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
// JUnit test for saveEmployee method
@DisplayName("JUnit test for saveEmployee method which throws exception")
@Test
public void givenExistingEmail_whenSaveEmployee_thenThrowsException(){
// given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.of(employee));
System.out.println(employeeRepository);
System.out.println(employeeService);
// when - action or the behaviour that we are going test
org.junit.jupiter.api.Assertions.assertThrows(ResourceNotFoundException.class, () -> {
employeeService.saveEmployee(employee);
});
// then
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
Employee employee1 = Employee.builder()
.id(2L)
.firstName("Tony")
.lastName("Stark")
.email("tony@gmail.com")
.build();
given(employeeRepository.findAll()).willReturn(List.of(employee,employee1));
// when - action or the behaviour that we are going test
List<Employee> employeeList = employeeService.getAllEmployees();
// 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
Employee employee1 = Employee.builder()
.id(2L)
.firstName("Tony")
.lastName("Stark")
.email("tony@gmail.com")
.build();
given(employeeRepository.findAll()).willReturn(Collections.emptyList());
// when - action or the behaviour that we are going test
List<Employee> employeeList = employeeService.getAllEmployees();
// 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_whenGetEmployeeById_thenReturnEmployeeObject(){
// given
given(employeeRepository.findById(1L)).willReturn(Optional.of(employee));
// when
Employee savedEmployee = employeeService.getEmployeeById(employee.getId()).get();
// then
assertThat(savedEmployee).isNotNull();
}
// JUnit test for updateEmployee method
@DisplayName("JUnit test for updateEmployee method")
@Test
public void givenEmployeeObject_whenUpdateEmployee_thenReturnUpdatedEmployee(){
// given - precondition or setup
given(employeeRepository.save(employee)).willReturn(employee);
employee.setEmail("ram@gmail.com");
employee.setFirstName("Ram");
// when - action or the behaviour that we are going test
Employee updatedEmployee = employeeService.updateEmployee(employee);
// then - verify the output
assertThat(updatedEmployee.getEmail()).isEqualTo("ram@gmail.com");
assertThat(updatedEmployee.getFirstName()).isEqualTo("Ram");
}
// JUnit test for deleteEmployee method
@DisplayName("JUnit test for deleteEmployee method")
@Test
public void givenEmployeeId_whenDeleteEmployee_thenNothing(){
// given - precondition or setup
long employeeId = 1L;
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);
}
}
注意,我们使用assertThat()
方法来断言使用AssertJ库的条件。
当我们想在多个地方使用模拟对象时,Mockito@Mock注解很有用。
当我们想将模拟对象注入另一个模拟对象时,我们可以使用@InjectMocks注解@InjectMock创建类的mock对象,并将用注解@mock标记的mock注入其中。
// JUnit test for saveEmployee method
@DisplayName("JUnit test for saveEmployee method")
@Test
public void givenEmployeeObject_whenSaveEmployee_thenReturnEmployeeObject(){
// given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.empty());
given(employeeRepository.save(employee)).willReturn(employee);
System.out.println(employeeRepository);
System.out.println(employeeService);
// when - action or the behaviour that we are going test
Employee savedEmployee = employeeService.saveEmployee(employee);
System.out.println(savedEmployee);
// then - verify the output
assertThat(savedEmployee).isNotNull();
}
// JUnit test for saveEmployee method
@DisplayName("JUnit test for saveEmployee method which throws exception")
@Test
public void givenExistingEmail_whenSaveEmployee_thenThrowsException(){
// given - precondition or setup
given(employeeRepository.findByEmail(employee.getEmail()))
.willReturn(Optional.of(employee));
System.out.println(employeeRepository);
System.out.println(employeeService);
// when - action or the behaviour that we are going test
org.junit.jupiter.api.Assertions.assertThrows(ResourceNotFoundException.class, () -> {
employeeService.saveEmployee(employee);
});
// then
verify(employeeRepository, never()).save(any(Employee.class));
}
@DisplayName("JUnit test for getAllEmployees method")
@Test
public void givenEmployeesList_whenGetAllEmployees_thenReturnEmployeesList(){
// given - precondition or setup
Employee employee1 = Employee.builder()
.id(2L)
.firstName("Tony")
.lastName("Stark")
.email("tony@gmail.com")
.build();
given(employeeRepository.findAll()).willReturn(List.of(employee,employee1));
// when - action or the behaviour that we are going test
List<Employee> employeeList = employeeService.getAllEmployees();
// then - verify the output
assertThat(employeeList).isNotNull();
assertThat(employeeList.size()).isEqualTo(2);
}
// JUnit test for getEmployeeById method
@DisplayName("JUnit test for getEmployeeById method")
@Test
public void givenEmployeeId_whenGetEmployeeById_thenReturnEmployeeObject(){
// given
given(employeeRepository.findById(1L)).willReturn(Optional.of(employee));
// when
Employee savedEmployee = employeeService.getEmployeeById(employee.getId()).get();
// then
assertThat(savedEmployee).isNotNull();
}
// JUnit test for updateEmployee method
@DisplayName("JUnit test for updateEmployee method")
@Test
public void givenEmployeeObject_whenUpdateEmployee_thenReturnUpdatedEmployee(){
// given - precondition or setup
given(employeeRepository.save(employee)).willReturn(employee);
employee.setEmail("ram@gmail.com");
employee.setFirstName("Ram");
// when - action or the behaviour that we are going test
Employee updatedEmployee = employeeService.updateEmployee(employee);
// then - verify the output
assertThat(updatedEmployee.getEmail()).isEqualTo("ram@gmail.com");
assertThat(updatedEmployee.getFirstName()).isEqualTo("Ram");
}
型
// JUnit test for deleteEmployee method
@DisplayName("JUnit test for deleteEmployee method")
@Test
public void givenEmployeeId_whenDeleteEmployee_thenNothing(){
// given - precondition or setup
long employeeId = 1L;
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);
}
型
在本教程中,我们讨论了如何使用JUnit和Mockito框架执行SpringBoot单元测试CRUDRESTAPI。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.javaguides.net/2022/03/spring-boot-unit-testing-service-layer.html
内容来源于网络,如有侵权,请联系作者删除!