如何测试findById方法?Mockito返回值类型错误排查
问题:Mockito报错WrongTypeOfReturnValue:Recipe不能被findById()返回
问题描述
已查阅相关话题但未解决,运行测试代码时触发Mockito类型错误,错误提示findById()应返回Optional,但实际返回了Recipe类型。
测试代码
@DisplayName("GET RecipeUltraLight by id is successful") @Test public void givenRecipeId_whenGetRecipeDetailsById_thenReturnRecipeObject(){ // given given(this.recipeRepository.findById(recipe.getId())).willReturn(Optional.of(recipe)); given(this.recipeService.getRecipeById(recipe.getId())).willReturn(recipe); given(this.recipeConverter.toUltraLight(recipe)).willReturn(recipeUltraLightDto); // when RecipeUltraLightDto retrievedRecipe = recipeService.getRecipeUltraLightById(recipe.getId()); // then verify(recipeRepository, times(1)).findById(recipe.getId()); verify(recipeService, times(1)).getRecipeById(recipe.getId()); verify(recipeConverter, times(1)).toUltraLight(recipe); assertThat(retrievedRecipe).isNotNull(); }
错误信息
org.mockito.exceptions.misusing.WrongTypeOfReturnValue: Recipe cannot be returned by findById() findById() should return Optional *** If you're unsure why you're getting above error read on. Due to the nature of the syntax above problem might occur because: 1. This exception *might* occur in wrongly written multi-threaded tests. Please refer to Mockito FAQ on limitations of concurrency testing. 2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
Service方法代码
@Transactional(readOnly = true) public RecipeUltraLightDto getRecipeUltraLightById(Long id) { Recipe recipe = getRecipeById(id); RecipeUltraLightDto dto = new RecipeUltraLightDto(); dto = recipeConverter.toUltraLight(recipe); return dto; } // internal use only @Transactional(readOnly = true) public Recipe getRecipeById(Long id) { if (id == null || id < 1) { return null; } return recipeRepository.findById(id) .orElseThrow(() -> new RecipeNotFoundException( String.format("Recipe with id %d not found.", id) )); }
测试类配置
@ContextConfiguration(classes = {RecipeService.class}) @ExtendWith({SpringExtension.class, MockitoExtension.class}) class RecipeServiceTest { @MockBean private RecipeConverter recipeConverter; @MockBean private RecipeRepository recipeRepository; @Autowired private RecipeService recipeService; private Recipe recipe; private RecipeUltraLightDto recipeUltraLightDto; @BeforeEach public void setup(){ recipe = Recipe.builder() .id(1L) .name("Recipe") .description("Description") .createdAt(LocalDateTime.now()) .difficulty(RecipeDifficulty.EASY) .minutesRequired(60) .portions(4) .authorId(1L) .views(0) .isVerified(false) .build(); recipeUltraLightDto = RecipeUltraLightDto.builder() .id(1L) .name("Recipe") .build(); } }
已尝试方案
- 使用Optional.ofNullable()
- 添加.isPresent()判断
- 移除.orElseThrow,改用if语句并调用.get()
- 尝试Kotlin实现
解决方法
核心问题
你错误地对被测试类(RecipeService)自身的方法getRecipeById进行了stub操作。RecipeService是通过@Autowired注入的真实实例,而非mock对象,当你用given(this.recipeService.getRecipeById(...))语法时,Mockito会将其视为spy对象处理,这种stub方式会触发内部方法调用,进而导致返回值类型不匹配的错误。同时,这个stub逻辑完全冗余——getRecipeById内部会调用已mock的recipeRepository.findById,只需保证repository的mock正确即可。
修改后的测试代码
@DisplayName("GET RecipeUltraLight by id is successful") @Test public void givenRecipeId_whenGetRecipeDetailsById_thenReturnRecipeObject(){ // given given(this.recipeRepository.findById(recipe.getId())).willReturn(Optional.of(recipe)); given(this.recipeConverter.toUltraLight(recipe)).willReturn(recipeUltraLightDto); // when RecipeUltraLightDto retrievedRecipe = recipeService.getRecipeUltraLightById(recipe.getId()); // then verify(recipeRepository, times(1)).findById(recipe.getId()); verify(recipeConverter, times(1)).toUltraLight(recipe); assertThat(retrievedRecipe).isNotNull(); assertThat(retrievedRecipe).isEqualTo(recipeUltraLightDto); // 新增断言,验证返回值正确性 }
关键调整说明
- 移除对
recipeService.getRecipeById的stub和verify:无需干预被测试类的内部方法调用,只需验证外部依赖(repository、converter)的行为和最终返回结果。 - 新增返回值匹配断言:确保方法返回的DTO与预期一致,提升测试严谨性。
内容的提问来源于stack exchange,提问作者Deyvid Dimitrov
相关产品推荐
相关产品推荐

