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

