Home > database >  Mockito not working when using Model Mapper
Mockito not working when using Model Mapper

Time:09-18

I have an User class:

public class User {
   private Long id;
   private String ssn;
   private float height;
   private float weight;
}

And an UpdateUserDTO class:

public class UpdateUserDTO {
   private float height;
   private float weight;
}

And an UserDTO class:

public class UserDTO {
   private Long id;
   private float height;
   private float weight;
}

I've used Model Mapper to map the updated fields from updateUserDTO to user, which comes from the DB.

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository repository;
    private final ModelMapper mapper;

    public UserDTO update(long userId, UpdateUserDTO updateUserDTO) {
        User user = getUserById(userId);
        mapper.map(updateUserDTO, user);
        user = repository.save(user);
        return mapper.map(user, UserDTO.class);
    }
}

My test class looks like this:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @InjectMocks
    private UserServiceImpl service;

    @Mock
    private ModelMapper mapper;

    @Test
    void updateWithSuccess() {
        long userId = 1L;

        UpdateUserDTO requestUpdateUserDTO = UpdateUserDTO.builder().height(1f).weight(1f).build();
        User userFromDB = User.builder().id(1L).ssn("123").height(50f).weight(50f).build();
        User userAfterMap = User.builder().id(1L).ssn("123").height(50f).weight(50f).build();
        UserDTO response = UserDTO.builder().id(1L).height(50f).weight(50f).build();

        Mockito.when(repository.findById(userId)).thenReturn(Optional.of(userFromDB));
        Mockito.when(mapper.map(requestUpdateUserDTO, userFromDB)).thenReturn(userAfterMap);
        Mockito.when(repository.save(userFromDB)).thenReturn(userFromDB);
        Mockito.when(mapper.map(userFromDB, UserDTO.class)).thenReturn(response);
    }

    service.update(userId, requestUpdateUserDTO);

    ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);

    assertEquals(requestUpdateUserDTO.getHeight(), userCaptor.getValue().getHeight());
    assertEquals(requestUpdateUserDTO.getWeight(), userCaptor.getValue().getWeight());
}

But the first Mockito.when() line does not even compile, because mapper.map() is a void method, so it does not expect any return values.

I've also tried:

Mockito.when(mapper.map(any(), any())).thenReturn(updatedUser);

Which does compile, but the mapper.map() doesn't work at all. Any of the fields are updated.

I've also tried:

Mockito.doReturn(updatedUser).when(mapper).map(any(), any());

But it doesn't work either.

I receive the error:

org.opentest4j.AssertionFailedError: 
Expected :5.0
Actual   :3.0

That's because mapper.map() does not work.

Does anyone know how I can mock this Mapper properly?

Thanks!

CodePudding user response:

What do you want to assert on? That the mocked mapper or repository are configured correctly?

The only thing you could assert here is that your builders work correctly, but I think that's the wrong test for that.

You don't have to assert on the results of the mocked stuff, you don't want to test Mockito itself, right?

If the mocks were not called correctly, you would get null there everywhere and therefore "earn" a NullPointerException. (So also the ArgumentCaptor is not necessary imo.)

What you should assert for is the result of the call to service.update, that it returns something equal to response. Make sure to implement equals and hashcode in your DTOs properly therefore!

And for the first call to mapper.map you don't need a rule what to return, but you should verify that it was called correctly.

UPDATE: example for my suggested method:

@Test
void updateWithSuccess() {
    final long userId = 1L;

    // not using the builders here just because I didn't want to implement them but used records instead.
    UpdateUserDTO requestUpdateUserDTO = new UpdateUserDTO(18f,36f);
    User userFromDB = new User(33L, "ssn", 17f, 35f);
    UserDTO response = new UserDTO(33L, 18f, 36f);

    Mockito.doReturn(Optional.of(userFromDB)).when(repository).findById(userId);
    Mockito.doReturn(userFromDB).when(repository).save(userFromDB);
    Mockito.doReturn(response).when(mapper).map(userFromDB, UserDTO.class);
    Mockito.doNothing().when(mapper).map(requestUpdateUserDTO, userFromDB);

    UserDTO result = service.update(userId, requestUpdateUserDTO);

    Assertions.assertEquals(response, result);
    Mockito.verify(mapper).map(requestUpdateUserDTO, userFromDB);
}

I suppose that the result from repository.findById is returned by service.getUserById. You also could use mocks for your User and UserDTO instances, but I used records for easier of rebuilding your test and as they are final the effort to mock them would be higher.

The problem you have in your test is that you use doReturn for the mocking of mapper.map(requestUpdateUserDTO, userFromDB), but this method does not return the new value, instead it modifies the second parameter. This is not supported by mocks - and not necessary for a test. You can just verify that the method was called (remember: you neither want to test the correct behaviour nor configuration of the ModelMapper with this test!)

  • Related