Battalgazi
Battalgazi

Reputation: 379

Mocked service function not working as expected

Here is my code. I can't see why it is not working. The problem is with the line in test :

when(applicationUserRepository.findApplicationUserByUsername("testUser"))
        .thenReturn(userToReturnFromRepository);

doesn't seem to be doing anything. The function findApplicationUserByUsername returns an empty optional when it should be returning an optional of userToReturnFromRepository.

Controller:

@RestController
@RequestMapping("api/v1/exercises")
public class ExerciseController {
    
    @Autowired
    ExerciseService exerciseService;

    @GetMapping
    public List<Exercise> getExercises() {

        List<Exercise> exercises = exerciseService.getAllExercises();
        return exercises;
    }
}

Service:

@Service("exerciseService")
public class ExerciseService {
    
    @Autowired
    ExerciseRepository exerciseRepository;

    @Autowired
    ApplicationUserRepository applicationUserRepository;
 
    @Transactional
    public List<Exercise> getAllExercises() {
        
        Principal principal = SecurityContextHolder.getContext().getAuthentication();

        Optional<ApplicationUser> applicationUser = applicationUserRepository.findApplicationUserByUsername(principal.getName());
                
        List<Exercise> exercises = new ArrayList<>();
        if(applicationUser.isPresent()){
            exercises = applicationUser.get().getExercises();
               
            }

        return exercises;
    }
}

The test:

@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {

    private final MockMvc mockMvc;
    private final ObjectMapper objectMapper;
    @Mock
    ApplicationUserRepository applicationUserRepository;
    
    @Autowired
    public ExerciseControllerTest(MockMvc mockMvc,
            ApplicationUserRepository applicationUserRepository, ObjectMapper objectMapper) {
        this.mockMvc = mockMvc;
        this.applicationUserRepository = applicationUserRepository;
        this.objectMapper = objectMapper;
    }

    @BeforeEach
    public void initMocks() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @WithMockUser(username = "testUser")
    public void testGetExercises() throws Exception {
        
         Exercise ex = new Exercise();
         ex.setData("test");
         ApplicationUser user = new ApplicationUser();
         Exercise[] exercises = {ex};
         List<Exercise> list = new ArrayList<Exercise>(Arrays.asList(exercises));
         user.setExercises(list);
         Optional<ApplicationUser> userToReturnFromRepository = Optional.of(user);

        when(applicationUserRepository.findApplicationUserByUsername("testUser"))
        .thenReturn(userToReturnFromRepository);

        mockMvc.perform(get("/api/v1/exercises")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(1)));


    }
}

Upvotes: 0

Views: 1256

Answers (1)

There are two conflicting things happening in your test:

  1. You are using Mockito to init a mock implementation via reflection
  2. You have ApplicationUserRepository being Spring injected into the Test class via the constructor.

What ends up happening is this:

  1. spring injects applicationUserRepository into the constructor param
  2. The applicationUserRepository field is set to the spring injected version in the constructor
  3. Mockito inits a new applicationUserRepository mock
  4. Mockito replaces the applicationUserRespository field with the mock (i.e. goodbye to your handle on the spring bean that your MVC setup is using!)

The easiest way I can think of to fix it is to use @MockBean instead of the @Mock combined with Constructor injection. @MockBean will instruct Spring to create the mock instance for you, use it, and provide it to you in the test.

@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {

    private final MockMvc mockMvc;
    private final ObjectMapper objectMapper;
    @MockBean // User MockBean instead of Mock
    ApplicationUserRepository applicationUserRepository;
    
    @Autowired
    public ExerciseControllerTest(MockMvc mockMvc, ObjectMapper objectMapper) {
        this.mockMvc = mockMvc;
        //remove the applicationUserRepository injection
        this.objectMapper = objectMapper;
    }

// remove MockitoAnnotations.openMocks(this);
//...

...
}

Upvotes: 2

Related Questions