1

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?

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, yep, I like integrity tests :)
id property of user changes to null after save method (mock) invoked in controller. "Real" save() doing exactly different, cant find what can sets user.id in "mock" version...
1

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

2 Comments

Agreed, but I have to be sure password of user was encoded, when new user was created using post("/user"). How I can check it?
I'm not sure of the complete structure of your app but unit tests should test one thing on one object per test, everything else should be mocked. If you are testing that something is called and something else encoded a password then that test is a candidate for two separate tests

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.