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

如何测试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); // 新增断言,验证返回值正确性
}

关键调整说明

  1. 移除对recipeService.getRecipeById的stub和verify:无需干预被测试类的内部方法调用,只需验证外部依赖(repository、converter)的行为和最终返回结果。
  2. 新增返回值匹配断言:确保方法返回的DTO与预期一致,提升测试严谨性。

内容的提问来源于stack exchange,提问作者Deyvid Dimitrov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 04:26:01