
在开发基于Spring Boot的应用时,我们经常会遇到需要对业务逻辑层(Service)和数据访问层(Repository)进行测试的场景。特别是对于执行“软删除”操作的void方法,其测试覆盖率的实现常常会引发疑问。本文将深入探讨如何为这类方法构建健壮的测试,涵盖单元测试和集成测试两种策略,确保代码的每个环节都得到充分验证。
在提供的示例中,userService.deleteUser方法负责业务逻辑,它首先查找用户,然后进行权限检查,最后调用userRepository.delete执行软删除。userRepository.delete方法通过@Modifying和@Query注解,将UserEntity的deleted字段设置为true,而非物理删除。
原始的测试代码deleteUserTest虽然能够验证userService中的异常逻辑,但它并未覆盖到userRepository.delete(userEntity)这一行的实际执行。这主要是因为:
为了实现全面的测试覆盖,我们需要采取分层测试的策略。
立即学习“Java免费学习笔记(深入)”;
对服务层的单元测试,其核心目标是验证业务逻辑的正确性,包括:
在这种情况下,我们应该模拟userRepository,并使用Mockito.verify()来确认userRepository.delete()方法是否被正确调用。
示例代码:改进的userService单元测试
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Date;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository; // 模拟UserRepository
@InjectMocks
private UserService userService; // 注入被测试的UserService
private UserEntity testUser;
private final String TEST_USER_ID = "1";
@BeforeEach
void setUp() {
testUser = new UserEntity();
testUser.setId(Integer.valueOf(TEST_USER_ID));
testUser.setName("Test User");
// 初始状态,通常为未删除
testUser.setDeleted(false);
}
/**
* 测试:当用户有效且满足访问策略时,userService应成功调用userRepository的delete方法。
*/
@Test
void deleteUser_shouldCallRepositoryDelete_whenValidUserAndAccessDate() {
// Arrange
testUser.setLastAccessDate(new Date()); // 设置lastAccessDate以满足策略
// 模拟userRepository.findById行为
when(userRepository.findById(Integer.valueOf(TEST_USER_ID)))
.thenReturn(Optional.of(testUser));
// Act
userService.deleteUser(TEST_USER_ID);
// Assert
// 验证userRepository.findById是否被调用了一次
verify(userRepository, times(1)).findById(Integer.valueOf(TEST_USER_ID));
// 验证userRepository.delete是否被调用了一次,并且传入的是正确的userEntity对象
verify(userRepository, times(1)).delete(testUser);
}
/**
* 测试:当用户不存在时,userService应抛出UserNotFoundException。
*/
@Test
void deleteUser_shouldThrowUserNotFoundException_whenUserNotFound() {
// Arrange
when(userRepository.findById(Integer.valueOf(TEST_USER_ID)))
.thenReturn(Optional.empty()); // 模拟用户不存在
// Act & Assert
assertThrows(UserNotFoundException.class, () -> userService.deleteUser(TEST_USER_ID));
// 验证userRepository.delete方法没有被调用
verify(userRepository, never()).delete(any(UserEntity.class));
}
/**
* 测试:当用户不满足访问策略(lastAccessDate为null)时,userService应抛出ProhibitedAccessException。
*/
@Test
void deleteUser_shouldThrowProhibitedAccessException_whenNoLastAccessDate() {
// Arrange
testUser.setLastAccessDate(null); // 设置lastAccessDate为null以违反策略
when(userRepository.findById(Integer.valueOf(TEST_USER_ID)))
.thenReturn(Optional.of(testUser));
// Act & Assert
assertThrows(ProhibitedAccessException.class, () -> userService.deleteUser(TEST_USER_ID));
// 验证userRepository.delete方法没有被调用
verify(userRepository, never()).delete(any(UserEntity.class));
}
}注意事项:
服务层的单元测试验证了业务逻辑和方法调用,但它没有验证userRepository.delete中@Query注解定义的SQL语句是否正确执行并更新了数据库。为了验证这一点,我们需要编写集成测试。
集成测试会启动一个部分或完整的Spring上下文,并与真实的(通常是内存中的)数据库进行交互。
示例代码:userRepository集成测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import java.util.Date;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
// @DataJpaTest注解用于测试JPA组件。它会配置一个in-memory数据库(如H2)
// 并自动扫描@Entity和Spring Data JPA仓库。
@DataJpaTest
class UserRepositoryIntegrationTest {
@Autowired
private UserRepository userRepository; // 注入真实的UserRepository
@Autowired
private TestEntityManager entityManager; // 用于在测试中管理实体(持久化、查找、刷新等)
/**
* 测试:验证userRepository的delete方法是否正确执行软删除操作。
*/
@Test
void delete_shouldSoftDeleteUser() {
// Arrange
// 创建一个UserEntity实例并设置初始状态
UserEntity user = new UserEntity();
user.setName("Integration Test User");
user.setDeleted(false); // 初始状态为未删除
user.setLastAccessDate(new Date()); // 设置一个日期,虽然对于仓库测试不严格需要,但保持一致性
// 使用TestEntityManager持久化用户到in-memory数据库
UserEntity persistedUser = entityManager.persistAndFlush(user);
// 清除EntityManager缓存,确保后续从数据库中获取的是最新状态
entityManager.clear();
// Act
// 从数据库中重新获取用户,确保操作的是一个受管理的实体
Optional<UserEntity> userOptional = userRepository.findById(persistedUser.getId());
assertThat(userOptional).isPresent();
UserEntity userToDelete = userOptional.get();
// 调用userRepository的delete方法执行软删除
userRepository.delete(userToDelete);
// 刷新EntityManager,确保SQL语句被执行到数据库
entityManager.flush();
// Assert
// 再次从数据库中查找用户,验证其deleted字段是否已更新为true
UserEntity softDeletedUser = entityManager.find(UserEntity.class, persistedUser.getId());
assertThat(softDeletedUser).isNotNull();
assertThat(softDeletedUser.isDeleted()).isTrue(); // 断言软删除成功
assertThat(softDeletedUser.getName()).isEqualTo("Integration Test User"); // 其他字段应保持不变
}
/**
* 额外测试:验证软删除的用户是否可以被正确查询(如果你的查询排除了软删除用户)。
* (这取决于你的findById或findAll方法是否考虑deleted字段)
*/
@Test
void findById_shouldReturnSoftDeletedUser_whenNotFiltered() {
// Arrange
UserEntity user = new UserEntity();
user.setName("Another Test User");
user.setDeleted(false);
user.setLastAccessDate(new Date());
UserEntity persistedUser = entityManager.persistAndFlush(user);
entityManager.clear();
UserEntity userToDelete = userRepository.findById(persistedUser.getId()).orElseThrow();
userRepository.delete(userToDelete);
entityManager.flush();
entityManager.clear();
// Act
Optional<UserEntity> foundUser = userRepository.findById(persistedUser.getId());
// Assert
// 默认的findById不会过滤deleted=true的,所以应该还能找到
assertThat(foundUser).isPresent();
assertThat(foundUser.get().isDeleted()).isTrue();
}
}注意事项:
为了全面测试Spring Boot中执行软删除的void方法,我们应该采用分层测试的策略:
单元测试服务层(Service Layer):
集成测试数据访问层(Repository Layer):
通过结合这两种测试方法,我们可以确保deleteUser方法的业务逻辑和底层数据库操作都得到了充分的验证,从而构建出更健壮、更可靠的应用程序。
以上就是Java Spring Boot中软删除Void方法的全面测试策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号