Use ArgumentCaptor outside of .verify()? - java

I have an entity which is not used as an argument in a .verify(), but only an attribute of that entity. However, I would like to capture the entity to later use assertEquals to verify another attribute of that entity. Is this possible if the entity is not used in the .verify() at all?
The thing I found out is that I can only capture an argument if I use it with .when() or .verify(). Neither makes sense in itself though, because the only class in which I use or return the entity directly as a parameter is the class I also want to test. So I use #Autowired for them and not #MockBean for example. If I try to include the entity inside the .verify() somehow, it gives a NullPointerException
ArgumentCaptor<Entity> entityArgumentCaptor = ArgumentCaptor.forClass(Entity.class);
Mockito.verify(reportMock, Mockito.times(1)).doIt(Mockito.eq(entityArgumentCaptor.capture().getIdentifier()), Mockito.eq(null)); //not working
Entity entityArguments = entityArgumentCaptor.getValue();
assertEquals(entityState.completed, entityArguments.getState());

You want to capture your entity, then assert its property:
ArgumentCaptor<Entity> entityArgumentCaptor
= ArgumentCaptor.forClass(Entity.class);
Mockito.verify(reportMock)
.doIt(Mockito.eq(entityArgumentCaptor.capture()), Mockito.isNull());
Entity entityArguments = entityArgumentCaptor.getValue();
assertEquals(
entityState.completed,
entityArguments.getIdentifier().getState());

Related

Using Lombok's SuperBuilder with Hibernate Validator (jakarta.validation.x) annotation on a container type leads to "Type mismatch"

I have a class ActivitiesModel which uses Lombok's SuperBuilder.
import jakarta.validation.NotBlank;
// other imports and statements omitted for brevity.
#Data
#SuperBuilder
#NoArgsConstructor
public class ActivitiesModel {
public static final String ACTIVITIES_NOT_NULL_MESSAGE = "Activities cannot be null";
public static final String ACTIVITY_NOT_BLANK_MESSAGE = "Activity cannot be blank";
#NotNull(message = ACTIVITIES_NOT_NULL_MESSAGE)
private List<#NotBlank(message = ACTIVITY_NOT_BLANK_MESSAGE) String> activities;
}
I am using this builder to create an object of ActivitiesModel, and then validating it using Hibernate's Validator interface:
// Somewhere else in the application.
// Create an object using the builder method.
ActivitiesModel activitiesModel = ActivitiesModel.builder()
.activities(List.of("hello", "world")) // <----- Point A
.build();
// Validate the object using Hibernate's validator.
validator.validate(activitiesModel);
However, running this code gives me the following error:
java.lang.Error:
Unresolved compilation problem:
Type mismatch: cannot convert from List<String> to List<E>
The stack trace seems to be pointing at Point A.
I have tried the following approaches:
Replacing the #SuperBuilder with #Builder and #AllArgsConstructor.
Replacing the message attribute with a string literal instead of a static final variable, i.e:
private List<#NotBlank(message = "Activity cannot be blank") String> activities;
1st approach seems to fix this error, however, it's not something I can use as I need to extend the builder functionality to a subclass of ActivitiesModel. Also, this issue is also present in another abstract class, so the super builder functionality for parent classes is definitely required.
2nd approach also works in solving the error. However, going with it is a bit problematic because I then need to have the same message string in the validation test for this model class, which is something I would like to avoid as it duplicates the string.
Another thing to note is that this error only seems to occur in the presence of an annotation on the generic type parameter of the container, which is NotBlank in this case. It is not influenced by any annotations which are present directly on the field itself (NotNull in this case).
So, all in all, these are the questions that I would like to get some answers to:
Somehow, Lombok is able to figure out the types in case of a string literal but not in case of a static final String. Why is that?
Am I going about this totally wrong? The problem occurs because I'm trying to store the message string in a variable, and I'm trying to re-use the same variable at two places: the annotation's message attribute, and in the validation test for the model class. Should I not be checking for the presence of the message in my validation tests, but be checking for something else instead?
For anyone who comes across this later on, the research for this issue has led me to believe that comparing message strings in tests is not the way to go about writing validation test cases. Another downside to this approach is that you might have different validation messages for different locales. In that case, the message string itself might be a template e.g. my.message.key with its values in a ResourceBundle provided to Hibernate, i.e. files such as ValidationMessages.properties and ValidationMessages_de.properties.
In such a scenario, you could compare message for one locale in your validation test case, however, a better approach might be to check the annotation and the field for which the validation has failed. We can get both of these pieces of information via the ConstraintViolation and subsequently the ConstraintDescriptor types, provided by Hibernate. This way we can circumvent checking the message itself, but rely on the actual validation annotation which has failed.
As for the solution to this question, it seems it was a build cache issue. Cleaning maven's build cache results in this code working perfectly fine, but VSCode still seems to have an issue. For now, I will choose to ignore that.

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

How to test Service method with ModelMapper call

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.

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.

graphql-java-tools: null parameter vs not present parameter

How can I distinguish in java graphQL if a parameter was explicitly set to null, or if it was not provided at all?
The use case that I try to achieve is the following: I have a mutation like this
updateUser(id:Int!, changes:UserChanges!)
#where UserChanges is defined as:
type UserChanges {
login:String
email:String
#etc
}
The idea here is that the user provides only the fields that he wants to change (like react setState for example).
So if email is ommited, I want to leave it unchanged.
But what if he/she wants to explicitly set email to null?
Is there any way figure this out from my resolver method and act accordingly?
(I use graphql-java-tools library)
I found the answer. In case somebody needs it:
An instance of graphql.schema.DataFetchingEnvironment is available to every resolver method.
This provides methods like getArguments(), hasArgument() etc.
Using those methods, we can find out if an argument was an explicit set to null, or if it was not provided at all.
Looks like deserialization from query/variables is handled by fasterxml Jackson, and that's proper place to deal with the issue, otherwise it becomes too complex: check every field? nested?
So: UserChanges.java should look like this:
class UserChanges {
// SHOULD NOT HAVE ALL ARGUMENT CONSTRUCTOR!
Optional<String> login;
Optional<String> email;
... getters & setters
}
in this case deserializer will use setters, ONLY FOR PROVIDED FIELDS!
And {"login":null} will become:
UserChanges.login = Optional.empty
UserChanges.email = null

Categories