Spring Boot中Mock并测试Delete服务的方法及相关疑问
问题解答
一、如何正确测试Delete服务?
你的测试目前存在核心问题:因为用了@Mock的UserRepository,调用underTest.addNewUser(userCreateDto)并不会真的存储数据,Mock对象默认不会执行真实持久化逻辑,所以调用deleteUserById(223L)时,userRepository.existsById(223L)会返回false,直接抛出IllegalStateException导致测试失败。
测试Delete服务需要覆盖两种核心场景:
- 用户存在时,成功执行删除操作,验证Repository的
deleteById方法被正确调用 - 用户不存在时,抛出指定的异常
修正后的测试代码示例:
@ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; private UserService underTest; @BeforeEach void setUp() { underTest = new UserService(userRepository); // 移除多余的@Autowired userService,你实际使用的是自己实例化的underTest } // 测试场景:用户存在时成功删除 @Test void shouldDeleteUserWhenExists() { final long userId = 223L; // 模拟Repository返回用户存在的状态 given(userRepository.existsById(userId)).willReturn(true); // 执行删除操作 underTest.deleteUserById(userId); // 验证deleteById被调用1次,参数正确 verify(userRepository, times(1)).deleteById(userId); } // 测试场景:用户不存在时抛出异常 @Test void shouldThrowExceptionWhenUserNotFound() { final long userId = 223L; // 模拟Repository返回用户不存在的状态 given(userRepository.existsById(userId)).willReturn(false); // 验证抛出指定异常及错误信息 assertThatThrownBy(() -> underTest.deleteUserById(userId)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("User with id " + userId + " doesn't exist"); // 验证deleteById从未被调用 verify(userRepository, never()).deleteById(userId); } }
是否需要先保存新用户?
- 单元测试(Mock Repository场景):不需要,通过Mock
existsById的返回值模拟用户存在/不存在的状态即可 - 集成测试(使用真实数据库):需要先插入测试数据,执行删除后再验证数据库中数据已被移除
二、API开发中Delete端点的返回设计
REST规范中,DELETE请求的标准返回逻辑:
- 成功删除:返回
204 No Content(无响应体),这是最符合REST规范的做法 - 若需要返回提示信息,也可以返回
200 OK并携带简单响应体(比如{"message": "用户删除成功"})
服务层的deleteUserById方法不需要返回boolean,原因:
- 成功执行时,没有需要返回给上层的业务数据
- 失败时通过抛出异常反馈错误,由控制器层捕获异常后转换为对应的HTTP状态码(比如用户不存在时返回404 Not Found)
修改后的服务层方法可改为void:
public void deleteUserById(Long userId) { if(!userRepository.existsById(userId)) { throw new IllegalStateException("User with id " + userId + " doesn't exist"); } userRepository.deleteById(userId); }
控制器层示例:
@DeleteMapping("/users/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUserById(id); return ResponseEntity.noContent().build(); // 返回204状态码 } // 全局异常处理器捕获自定义异常 @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalStateException.class) public ResponseEntity<String> handleIllegalState(IllegalStateException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); } }
三、哪些服务方法更适合设为void?
满足以下条件的服务方法适合用void:
- 执行操作后,没有需要返回给上层的业务数据(比如删除操作,仅需确认成功即可)
- 操作失败时通过抛出异常反馈错误,而非返回错误标识
- 批量操作(比如批量更新状态、批量删除),仅需确保操作完成,无需返回结果集
典型例子:
void deleteUserById(Long id)void updateUserStatus(Long id, UserStatus status)void batchDeleteUsers(List<Long> userIds)
反之,如果方法需要返回业务数据(比如查询用户、创建用户后返回实体),则需返回对应的对象。
四、职场中编写测试的频率
在正规开发团队中:
- 单元测试:核心业务逻辑的服务层方法必须编写,通常与开发同步进行(写完一个服务方法就写对应测试用例),覆盖率要求一般在60%-80%以上(核心模块要求更高)
- 集成测试:API端点、数据库交互等场景,在功能开发完成后编写,确保端到端流程正常
- 自动化测试:集成到CI/CD流程中,每次代码提交都会自动运行测试用例,避免破坏现有功能
简单来说,只要涉及核心业务的代码,几乎都需要编写对应的测试用例,测试与开发并行,而非事后补充。
内容的提问来源于stack exchange,提问作者expectedNoneExpected
相关产品推荐
相关产品推荐

