Mockito中Optional.map().orElseThrow()的Mock失效问题排查
Mockito Mock在Optional.map链式调用中未生效问题排查
问题场景
在Udemy课程的单元测试中,测试ProjectService的createGroup方法时遇到异常:尽管已经mock了projectRepository.findById返回存在的Project,但执行时仍抛出java.util.NoSuchElementException: No project with ID 99 found。
待测试的原方法
public GroupReadModel createGroup(LocalDateTime deadline, Integer projectId) { if (!configurationProperties.getTemplate().isAllowMultipleTasks() && taskGroupRepository.existsByDoneIsFalseAndProject_Id(projectId)) { throw new IllegalStateException("Only one undone group form project is allowed"); } TaskGroup result = projectRepository.findById(projectId) .map(project -> { TaskGroup taskGroup = new TaskGroup(); taskGroup.setDescription(project.getDescription()); taskGroup.setTasks(project.getProjectSteps().stream() .map(step -> Task.createNewTask(step.getDescription(), deadline.plusDays(step.getDaysToDeadline()))) .collect(Collectors.toSet())); taskGroup.setProject(project); return taskGroupRepository.save(taskGroup); }).orElseThrow(() -> new NoSuchElementException(String.format("No project with ID %d found", projectId))); return new GroupReadModel(result); }
测试方法
@ExtendWith(SpringExtension.class) class ProjectServiceTest { @Autowired private ProjectService projectService; @MockBean private ProjectRepository projectRepository; @MockBean private TaskGroupRepository taskGroupRepository; @MockBean private TaskConfigurationProperties configurationProperties; @Mock private TaskConfigurationProperties.Template template; @TestConfiguration static class ProjectServiceTestConfig { @Bean ProjectService projectService(ProjectRepository projectRepository, TaskGroupRepository taskGroupRepository, TaskConfigurationProperties configurationProperties ){ return new ProjectService(projectRepository, taskGroupRepository, configurationProperties); } } @Test void should_return_new_group_read_model() { //given LocalDateTime deadline = LocalDateTime.now(); Integer projectId = 99; Project projectById = new Project(projectId, "test project"); projectById.setProjectSteps(Set.of(new ProjectStep("test1", 2))); TaskGroup taskGroupSaved = TaskGroup.CreateNewTaskGroup(projectById.getDescription(), Set.of(Task.createNewTask("test1", LocalDateTime.now()))); GroupReadModel expectedResult = new GroupReadModel(taskGroupSaved); expectedResult.setDeadline(expectedResult.getDeadline().plusDays(2)); Mockito.when(configurationProperties.getTemplate()).thenReturn(template); Mockito.when(template.isAllowMultipleTasks()).thenReturn(true); Mockito.when(taskGroupRepository.existsByDoneIsFalseAndProject_Id(projectId)).thenReturn(false); Mockito.when(projectRepository.findById(projectId)).thenReturn(Optional.of(projectById)); //when GroupReadModel result = projectService.createGroup(deadline, projectId); //then assertEquals(expectedResult, result); } }
特殊现象
将原方法拆分为先获取Project再处理的写法后,mock能正常工作,不会抛出异常:
Project projectById = projectRepository.findById(projectId) .orElseThrow(() -> new NoSuchElementException(String.format("No project with ID %d found", projectId))); TaskGroup taskGroup = new TaskGroup(); taskGroup.setDescription(projectById.getDescription()); taskGroup.setTasks(projectById.getProjectSteps().stream() .map(step -> Task.createNewTask(step.getDescription(), deadline.plusDays(step.getDaysToDeadline()))) .collect(Collectors.toSet())); taskGroup.setProject(projectById); taskGroupRepository.save(taskGroup);
原因分析
问题的核心在于**taskGroupRepository.save方法的mock缺失**:
- 在原方法的
Optional.map链式调用中,map的lambda表达式最终返回taskGroupRepository.save(taskGroup)的结果。 - Mockito中未mock的方法默认返回
null,所以save调用返回null,而Optional.map会将映射函数返回的null转换为Optional.empty()。 - 后续的
orElseThrow遇到Optional.empty()就会触发预设的NoSuchElementException,看起来像是projectRepository.findById的mock未生效,但实际上是map阶段导致整个Optional变为空。
而拆分写法中,代码并没有将save的返回值作为最终的TaskGroup结果,orElseThrow在获取Project阶段就已经执行完成,所以即使save返回null也不会触发异常。
解决方案
在测试代码的given阶段添加对taskGroupRepository.save的mock,指定它返回预设的taskGroupSaved对象:
// 在given部分添加这一行 Mockito.when(taskGroupRepository.save(Mockito.any(TaskGroup.class))).thenReturn(taskGroupSaved);
添加后,map函数中的save调用会返回预设的TaskGroup实例,Optional不会变为空,orElseThrow就不会触发,测试就能正常执行。
如果需要验证save方法确实被调用,可以在then阶段添加验证:
Mockito.verify(taskGroupRepository).save(Mockito.any(TaskGroup.class));
内容的提问来源于stack exchange,提问作者Pioter88
相关产品推荐
相关产品推荐

