You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Mock并测试Delete服务的方法及相关疑问

问题解答

一、如何正确测试Delete服务?

你的测试目前存在核心问题:因为用了@Mock的UserRepository,调用underTest.addNewUser(userCreateDto)并不会真的存储数据,Mock对象默认不会执行真实持久化逻辑,所以调用deleteUserById(223L)时,userRepository.existsById(223L)会返回false,直接抛出IllegalStateException导致测试失败。

测试Delete服务需要覆盖两种核心场景:

  1. 用户存在时,成功执行删除操作,验证Repository的deleteById方法被正确调用
  2. 用户不存在时,抛出指定的异常

修正后的测试代码示例:

@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场景):不需要,通过MockexistsById的返回值模拟用户存在/不存在的状态即可
  • 集成测试(使用真实数据库):需要先插入测试数据,执行删除后再验证数据库中数据已被移除

二、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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:05:44