zzheads
zzheads

Reputation: 1472

Using Mock when method

Writing some tests for my webapp with API, testing controllers methods. Controllers uses service methods which I want to mock. Test code:

@Mock
private UserService userService;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserApi userApi;

    MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.standaloneSetup(userApi).build();

@Test
public void addUser() throws Exception {
    Role role = new Role(1L, "ROLE_USER");
    User user = new User (null, "Test user", "Test password", true,  role);
    when(roleService.findByName(role.getName())).thenReturn(role);
    when(passwordEncoder.encode(user.getPassword())).thenReturn("Encoded password");
    when(userService.save(user)).thenReturn(1L);


    mockMvc.perform(MockMvcRequestBuilders.post("/user").content(user.toJson())).andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json"))
            .andExpect(content().string(user.toJson()));

}

When UserService.save(user) called it sets id property of user to some unique Long value if it was null. Can I set Mock of UserService.save(user) to change id of saved user, as real save() do? Same with PasswordEncoder, when PasswordEncoder.encode(string) is called it changes string value to encoded value, how say Mock of PasswordEncoder do the same?

Upvotes: 1

Views: 3146

Answers (2)

Tunaki
Tunaki

Reputation: 137084

Wanting this is a test smell and probably means you are testing too much; thus, I would not recommend it. Your unit test is testing the Spring controller, and it is not testing the UserService or the PasswordEncoder, and it is not the responsility of the Spring controller to make sure the id of the user was changed, or that the password was encoded. You could verify that both userService and passwordEncoder wher invoked, but that's as far as testing the controller should go.

It sounds like what you really want to do is not mock UserService and PasswordEncoder, but, instead, perform an end-to-end test with the real classes, on a testing database for example. You can POST your new user starting at the controller, then fetch it back from the testing database and verify that the result you have is the right one. In this scenario, you're not unit-testing the Spring controller, but the whole real chain.

That said, you could do what you're asking by setting a custom answer using thenAnswer instead of thenReturn:

when(userService.save(user)).thenAnswer(new Answer<Long>() {
    @Override
    public Long answer(InvocationOnMock invocation) {
        user.setId(1L);
        return 1L;
    }
});

When the method is invoked on the mock, this custom answer will be called. Its result will be what is returned by the mocked method. Here, it also sets the id of the user before returning. This is still quite horrible, and it would be a lot preferable to have a proper integration test.

Upvotes: 1

Ross Drew
Ross Drew

Reputation: 8246

Assuming you are unit testing and not functional/integration testing, if you are mocking UserService and User then you don't care about their behavior, you are "mocking" it. This means you can just tell it to return a previously setup User or a mock of a User.

i.e. just set the user id when you create the User object

Upvotes: 1

Related Questions