How to test Service method with ModelMapper call - java

I'm writing some Unit Tests for my Service class, specifically, an update method that does exactly that, update an Entity with the given data from a request.
The problem is, I'm using ModelMapper to map the request data to the entity and when the test goes through the mapping statement it doesn't actually call the modelMapper but the mock .... which does nothing because it's a mock.
How should I mock its behavior?
public EntityNode updateEntity(String code, EntityDTO request) {
String message = "Entity with code: %s not found";
EntityNode entity = repository.findByCode(code)
.orElseThrow(() -> new EntityNotFoundException(String.format(message, code)));
modelMapper.map(request, entity);
return repository.save(entity);
}
I've thought about using an ArgumentCaptor but I'm not really sure if it suits my needs or if it's really what I need to do what I want.
This is my unfinished test method. After writing all of this I think I should stub ModelMappers.map() somehow and also return the result of calling the ModelMapper stub map() method when calling repository.save(entity).
#Test
void givenValidEntity_whenUpdateEntity_shouldUpdateProperties() {
//given
String code = "TEST";
Entity expected = new Entity();
expected.setName("Old");
EntityDTO request = new EntityDTO();
request.setName("New")
given(repository.findByCode(code)).willReturn(expected);
//when
Entity updatedEntity = service.updateEntity(code, request);
//then
assertEquals(request.getName(), updatedEntity.getName());
}
Does this make any sense?
Thanks

What does the changing?
By looking at the current code it seems like the modelMapper does the changing. This would mean that changing unit test should be in modelMapper's own unit test.
What does the EntityNode updateEntity(String code, EntityDTO request) do?
It fetches an entity from a repository, takes your entity, passes it through modelMapper and saves the result via repository. So while testing this function you should test only that those things happen with correct arguments, not what the modelMapper itself does.
If you want to test your function + modelMapper then this is more of an integration test not a unit test.
Additional notes
Ideally modelMapper would not mutate anything. Instead it would take the two arguments and return the result as a new object. This way it would be more easily testable and mockable (you could avoid using argument verification while testing updateEntity function).

You could extract the mapping to another class that returns the mapped entity, so you could mock that returned value.

Related

Create Generic method for mapping of entity to dto in spring boot

I have written a method which map the entity to dto using modelMapper, now I want to reuse it for other entities, so I want to transform the below method into generic type.
private VehicleImageAndLayoutDTO mapToDto(MsilVehicleLayout msilVehicleLayout) {
log.info("MsilVehicleLayoutServiceImpl::mapToDto::START");
this.modelMapper = new ModelMapper();
TypeMap<MsilVehicleLayout, VehicleImageAndLayoutDTO> propertyMapper = this.modelMapper
.createTypeMap(MsilVehicleLayout.class, VehicleImageAndLayoutDTO.class);
// propertyMapper.addMappings(skipFieldsMap);
propertyMapper.addMappings(mapper -> mapper.using(v -> Boolean.TRUE).map(MsilVehicleLayout::getId,
VehicleImageAndLayoutDTO::setMsil));
VehicleImageAndLayoutDTO imageAndLayoutDto = this.modelMapper.map(msilVehicleLayout,
VehicleImageAndLayoutDTO.class);
return imageAndLayoutDto;
}
Now the problem is that, it is tightly coupled because of below line,
propertyMapper.addMappings(mapper -> mapper.using(v -> Boolean.TRUE).map(MsilVehicleLayout::getId,
VehicleImageAndLayoutDTO::setMsil));
because i am using model mapper, but few properties I want to set with static values, so the above code is nothing, but setting the setMsil method of VehicleImageAndLayoutDTO with Boolean.TRUE.
Now the first argument MsilVehicleLayout::getId is just like a mock argument because mapper.map() expecting two argument, so first argument is just mock one and has nothing to do with VehicleImageAndLayoutDTO::setMsil
Now, I want to figured out a way to either isolate these dependency out of this snippet or is there a way to leverage the Reflection API in order to set the custom values ??
this is the problem statement which I am expecting the answer for, please help

Is asserting mocked object and method necessary in Unit Test(JUnit 5)?

I'm still a basic learner for Unit Test.
I'm trying to mock my class's method without directly calling it(since it is dependent on 3rd party library) and I wrote a test method like below.
(The 3rd party library which I mentioned is not MapStruct, it is ModelObject.class and it has very complicated parameters for the constructor and is only available to initialize in library level)
The ProjectMapper class's toDto method is a simple object mapping method(with MapStruct library).
#Test
#DisplayName("Should convert Project ModelObject to Project DTO successfully.")
void testProjectModelObjectToDtoSucess() {
// Given
ModelObject mockModelObject = mock(ModelObject.class); // <-- Mocking "ModelObject.class"
ProjectDto expected = new ProjectDto("PPJT-00000001", "Test Project 01"); // <-- Initializing expected return object.
when(mockProjectMapper.toDto(mockModelObject)).thenReturn(expected); // <-- Mocking cut(class under test)'s method.
// When
ProjectDto actual = mockProjectMapper.toDto(mockModelObject); // <-- Calling mocked method.
// Then
assertEquals(actual.getId(), expected.getId(), "The converted DTO's ID should equal to expected DTO's ID.");
assertEquals(actual.getName(), expected.getName(), "The converted DTO's name should equal to expected DTO's name.");
}
What I want to know is if I already assume mockProjectMapper.toDto() will return exactly equaled expected object, why would I need to assert with the actual(returned) object?
I learned that Unit Test should test any codes which can be breakable.
I doubt what's the benefit of this way of the test and if it's inappropriate, what's the correct way to test this method with mocking?
I referenced this test method from this example code.
#refael-sheinker Here's the source code of the ProjectMapper class which is MapStruct's interface class.
#Mapper
public interface ProjectMapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class);
#Mapping(target = "id", source = "projectId")
#Mapping(target = "name", source = "projectName")
ProjectDto toDto(ModelObject modelObject);
}
You mocked your object under test and stubbed method under test - and you correctly concuded that such a test brings no benefit.
You are checking that you can stub a method, not your production code.
The main problem is your assumption that you need to mock every 3rd party class. This is wrong way of thinking - you should try to mock collaborators of your class (especially ones that cause side-effects unwanted in the test environment), but not the classes which are critical part of implementation of your class. The line might be sometimes blurry, but in this concrete example I strongly recommend NOT mocking the mapper:
your 3rd party class is driven by annotations
the annotations must correspond to your DTOs fields
the field names in annotations are Strings, their presence is DTOs is not enforced by compiler
all mapping is done in the memory
Creating a test that exercises production code gives you confidence that:
field names are correct
corresponding mapped fields have correct types (for example, you are not mapping int to boolean)
The linked test of the service is somewhat different: it tests a Spring service which calls the repository. The repository presumably represents some DB, which interacts with the outside world - it is a good candidate for a mock. To my eyes the service method only forwards the call to the repository - which is a trivial behaviour - so the test is trivial, but this setup will scale for more complicated service methods.
Update
Mocking source class for MapStruct.
If ModelObject is complicated to create, consider using mocks for some of its constructor parameters.
var pN = mock(ParamN.class);
// possibly stub some interactions with pN
var modelObject = new ModelObject(10, 20, pN);
Alternatively, you could even mock entire ModelObect and stub its getters:
var mockModelObject = mock(ModelObject.class);
when(mockModelObject.getId()).thenReturn(10);

How to mock a repository method no matter the parameters with mockito

I'm using spring framework and testing with Junit and mockito.
Right now I have a method in my service which is using a few objects I create in the test, let's call them configObject1 and configObject2, I send them to the method as a parameter, and then the method start making some calls to another repositories along those configuration objects, those repositories are mocked and work well, the method makes a List of "CalculusResult" from those queries/configObjects. After that I use a repository extending CRUDRepository and make a saveAll(). Then it should return an iterable with the entities, but for some reason after the saveAll method it returns an empty list.
Test:
#Test
...
configObject1 conf1 = new configObject1 (...);
configObject2 conf2 = new configObject2 (...);
Calculusresult calcRes = new CalculusResult(null,...,new java.sql.Date(system.currentTimeMilis()),...);
List<CalculusResult> resList= new ArrayList<CalculusResult>();
resList.add(calcRes);
Calculusresult calcRes2 = new CalculusResult(1,...,new java.sql.Date(system.currentTimeMilis()),...);
List<CalculusResult> resList2= new ArrayList<CalculusResult>();
resList2.add(calcRes2);
when(calculusResultRepository.saveAll(resList)).thenReturn(resList2);
...
assertTrue(!response.isEmpty())
Method from the service:
...//The method is building the list of calculusResults
resCalc.setDate(new java.sql.Date(system.currentTimeMilis()))
resList.add(calcres);//CalculusResult object is added to the list, this object is well made
List<CalculusResult> savedResults = (List<CalculusResult>) calculusResultRepository.saveAll(resList); //This returns an empty list (If I don't cast, it also returns nothing)
for(CalculusResult calcres : savedResults){
... //This makes nothing because savedResults is empty, making the response empty and failing the test.
Repository:
#Repository
public interface CalculusResultRepository extends CrudRepository<CalculusResult, Long> {
}
I'm not sure but I think the problem may be that the object I'm creating in the test is different to the one in the service because one of the attributes is an sql Date of the moment it's created, so maybe it's not triggering "when(calculusRepository.saveAll(reslist)..." in the test because the object created in the test and the one created in the service have different Dates in that atribute.
If that's the case, is there a way to fix it? Or is the problem a completely different thing?
You can use Mockito ArgumentMatchers to match any argument.
when(calculusResultRepository.saveAll(Mockito.any(List.class)))
.thenReturn(resList2);

Mock Hibernate entityManager with createQuery(), parameters and executeUpdate()

I'm new on Unit Testing in java; I've an application that uses Hibernate to interact with a MySQL database.
I have many queries built with createQuery() method, also with parameters, like the following one:
return this.entityManager.createQuery("from MyEntity m where param = :param", MyEntity.class)
.setParameter("param", param)
.getSingleResult();
I would like to avoid to mock all the subsequent calls on the entityManager object, because sometimes I've query with more than 5 parameters and seems not that handy to mock each of those calls.
The same concept can be applied on Builder objects.
Edit 1
I add a concrete example of what I use (given that it's not a good way to manage exception, but unluckly is quiet usual):
public class MyService {
private EntityManager entityManager;
public MyEntity find(String field ) {
try{
return this.entityManager.createQuery("from MyEntity c where c.field = :field ", MyEntity .class)
.setParameter("field ", field )
.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (NonUniqueResultException e) {
logger.error("find", e);
return null;
}
}
}
In this example, given the behavior of the call on entityManager I have different branches to be tested. Then I have to mock the answer of that call to test all the lines of this method.
What I found
What I found was the following:
#Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityManager entityManager;
Which works as expected. I can mock all the calls' chain. BUT
Citing from the Javadoc of Mockito.RETURNS_DEEP_STUBS:
WARNING: This feature should rarely be required for regular clean code! Leave it for legacy code. Mocking a mock to return a mock, to return a mock, (...), to return something meaningful hints at violation of Law of Demeter or mocking a value object (a well known anti-pattern).
If the previous point wasn't enough, the next one, some lines after, clearly set a big limitation:
This feature will not work when any return type of methods included in the chain cannot be mocked (for example: is a primitive or a final class). This is because of java type system.
The second point means that if I try to mock in this way the method executeUpdate(), which returns an int, it raise an exception.
when(entityManager.createQuery(anyString())
.setParameter(eq("param"), anyString())
.executeUpdate())
.thenReturn(1);
and in that way I can't test the interactions with the entityManager.
Questions
How should I mock the calls on entityManager? It seems impossible to me that I have to mock each method one by one.
Is wrong to use Answers.RETURNS_DEEP_STUBS? If not, how can I handle the second example?
Don't mock the JPA API, just write integration tests with proper test data and execute the real queries against real data to see if everything works. Projects like testcontainers make it very easy to get started.

Mockito Issue in mocking a list

I want to mock a list
private Item populateData(Item i) {
List<Person> groupIdList = xyzDao.getData(id);
for (Person p: groupIdList) {
}
}
I want to testing the function by mocking groupIdList how to perform this ?
The official Mockito documentation is a bit misleading. You should not mock list (data container without much logic), you should mock behaviours. In your case xyzDao.getData(id) is the behaviour. Mock xyzDao and return some bogus data:
//given
XyzDao xyzDaoMock = mock(xyzDao);
//inject to XyzService class under test
given(xyzDaoMock.getData(id)).willReturn(Arrays.asList(...));
//when
xyzService.populateData() //...XyzService uses mocked XyzDao
//then
In then section you should either verify() that xyzDao was called or make sure correct list was returned. Hard to tell what you need based on your code snippet.
Or the non-BDD version of Tomasz' answer (but accept his answer over mine, if appropriate):
XyzDao xyzDaoMock = mock(xyzDao);
when(xyzDaoMock.getData(id)).thenReturn(Arrays.asList(...));
xyzService.populateData() //...XyzService uses mocked XyzDao

Categories