Search code examples
springunit-testingjpajunitmockito

How to test findById method?


First - I've checked all previous topics around this question and none of them helped.

Having the following code:

    @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();
    }

gives me this error:

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 method:



    @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)
                ));
    }

Setup:

@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();
    }

I've tried:

  1. Optinal.ofNullable()
  2. Adding .isPresent()
  3. Getting rid of .orElseThrow and going through if statements and using .get()
  4. Kotlin

Will be glad if someone can help.


Solution

  • You are creating a mock of the object you are testing and with that basically also render the mocking of the repository useless.

    You should remove the line given(this.recipeService.getRecipeById(recipe.getId())).willReturn(recipe); that way it will just call the method and call the repository. Which now will return the mocked result. As that is the behavior that will now kick in.