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

Mockito-JUnit测试RestController删除接口不生效问题排查

问题排查与解决方案

让我们一步步拆解你的问题并解决它:

首先,分析核心问题

1. Service层方法逻辑缺陷

你的deletePerson方法不管是否找到对应的Person,都会执行personRepository.deleteById(id)——这意味着即使目标ID不存在,你还是会尝试执行删除操作,完全不符合“不应从数据库删除对象”的测试场景。而且这个方法在找不到Person时,只是返回空Optional,并没有抛出你定义的NoEntityFoundException。

2. 测试逻辑与真实代码流程不匹配

你在测试中mock了personService.deletePerson(1L)直接抛出异常,但实际代码里(从错误栈能看出来),NoEntityFoundException是Controller层在Service返回空Optional时才抛出的,不是Service直接抛的。这种mock行为和真实流程不符,导致异常没有被正确处理,直接穿透到Servlet层引发500错误。

3. 缺少全局异常处理机制

Spring默认会把未捕获的RuntimeException转换成500错误,你没有配置全局异常处理器来将NoEntityFoundException映射为你期望的400 Bad Request(或者更符合REST规范的404 Not Found),所以测试时会抛出嵌套Servlet异常。


分步解决方案

第一步:修复Service层的业务逻辑

先让Service只在找到Person时执行删除,符合“不存在则不删除”的需求:

public Optional<PersonDto> deletePerson(Long id) {
    Assert.notNull(id, "id can't be null");
    Optional<Person> personOptional = personRepository.findById(id);
    
    // 仅当找到Person时才执行删除操作
    personOptional.ifPresent(person -> personRepository.deleteById(id));
    
    return personOptional.map(p -> modelMapper.map(p, PersonDto.class));
}

第二步:确保Controller层正确处理空Optional

假设你的Controller代码是类似这样的,确保当Service返回空Optional时抛出异常:

@DeleteMapping("/people/{id}")
public ResponseEntity<Void> deletePerson(@PathVariable Long id) {
    Optional<PersonDto> deletedPerson = personService.deletePerson(id);
    
    // 找不到对应资源时抛出自定义异常
    if (deletedPerson.isEmpty()) {
        throw new NoEntityFoundException();
    }
    
    return ResponseEntity.noContent().build();
}

第三步:添加全局异常处理器

创建一个全局异常处理类,把自定义异常转换成标准HTTP响应(这里建议用404 Not Found,因为资源不存在更符合这个状态码的语义,你也可以根据需求改成400):

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoEntityFoundException.class)
    public ResponseEntity<String> handleNoEntityFoundException(NoEntityFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                             .body(ex.getMessage());
    }
}

第四步:修正测试方法,匹配真实流程

现在测试应该mock Service返回空Optional,模拟找不到Person的场景,这样Controller会抛出异常,再被全局处理器转换成对应的HTTP状态:

@Test
public void shouldNotDeletePersonByGivenId() throws Exception {
    // Mock Service返回空Optional,模拟目标ID不存在的情况
    Mockito.when(personService.deletePerson(1L)).thenReturn(Optional.empty());
    
    mockMvc.perform(delete("/people/{id}", 1L))
           // 如果你选择用400就改成isBadRequest(),这里用更合理的404
           .andExpect(status().isNotFound())
           .andExpect(content().string("There is no Entity in database with this id."));
}

可选方案:让Service直接抛出异常

如果你更倾向于让Service层负责抛出异常(而不是返回空Optional),可以修改Service方法如下:

public PersonDto deletePerson(Long id) {
    Assert.notNull(id, "id can't be null");
    // 找不到直接抛异常
    Person person = personRepository.findById(id)
            .orElseThrow(NoEntityFoundException::new);
    
    personRepository.delete(person);
    return modelMapper.map(person, PersonDto.class);
}

对应的测试就可以直接mock Service抛出异常:

@Test
public void shouldNotDeletePersonByGivenId() throws Exception {
    Mockito.when(personService.deletePerson(1L)).thenThrow(new NoEntityFoundException());
    
    mockMvc.perform(delete("/people/{id}", 1L))
           .andExpect(status().isNotFound())
           .andExpect(content().string("There is no Entity in database with this id."));
}

内容的提问来源于stack exchange,提问作者Magdalena Rumakowicz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:45:39