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!)