AssertJ: How to build custom and nested assertions - assertj

I need to compare one PatientDTO dto object with other one PatientModel model object.
Both classes are quite similar:
class PatientDTO {
private String name;
private List<AddressDTO> address;
// Constructors, getters and setters
}
class PatientModel {
private String id;
private String nameElement;
private List<AddressModel> addressElement;
// Constructors, getters and setters
}
class AddressDTO {
private String city;
private String country;
private List<String> linesElement;
// Constructors, getters and setters
}
class AddressModel {
private String city;
private String countryElement;
private List<String> linesElement;
// Constructors, getters and setters
}
Main differences are:
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
Other issue I like to solve, is that:
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
I'd like to build an assertion like this:
PatientDTO patientDTO;
PatientModel patientModel;
assertThat(patientDTO).isEqual(patientModel);
Shortly:
Should I build a custom assertion?
Should I have an assertion for Address an other one for Patient containing previous Address assertion? How could I get this?
What aboud Address assertion for Patient, Organization
What I want to avoid is code like this:
assertThat(patientDTO).anySatisfy(p->{
assertThat(p.getName()).withFailMessage("expected name: "+ p.getAddress().getCity()).isEqualTo(patientModel.getNameElement());
assertThat(p.getAddress().getCity()).withFailMessage("expected city: "+ p.getAddress().getCity()).isEqualTo(patientModel.getCityElement());
assertThat(p.getAddress().getCountry()).withFailMessage("expected country: "+ p.getAddress().getCountry()).isEqualTo(patientModel.getCountryElement());
...
}
);
I want to avoid above code since Patient classes are really large. Here I've shorted them for clarity purpouses.
Any ideas?

The field-by-field recursive comparison could help for this purpose:
PatientDTO patientDTO = new PatientDTO(...);
PatientModel patientModel = new PatientModel(...);
assertThat(patientDTO).usingRecursiveComparison()
.isEqualTo(patientModel);
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
There are a few methods that can be used to tune the comparison and ignore fields:
Directly with ignoringFields(String…​ fieldsToIgnore)
By regexes with ignoringFieldsMatchingRegexes(String…​ regexes)
By types with ignoringFieldsOfTypes(Class…​ typesToIgnore)
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
This is currently not supported and was also asked in https://stackoverflow.com/a/70381488/9714611. We plan to raise a feature request about it and I will update the answer once the issue link is ready.
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
If the target is always isEqualTo, probably a custom assertion implementation is not needed as long as the limitation of the recursive comparison about not being able to compare fields with different names is not a show-stopper. These fields would require ad-hoc comparison until a better solution is available.
If the target is to provide assertions in a domain-specific language, like:
assertThat(patientDTO).hasAddress(addressDTO);
then a custom assertion implementation can be added.
Also, there is an assertions generator with plugins for Maven and Gradle that can be used to generate assertions based on the class attributes.

Related

Kotlin JPA entity ID

What is "the kotlin way" to define JPA entity ID?
#Entity
data class User (
#Id #GeneratedValue
var id: Long? = null,
...
)
Or is there any better one to avoid nullable id?
You can use a 0 value rather than a null value.
#Entity
data class User (
#Id #GeneratedValue
var id: Long = 0,
...
)
Autogeneration should still find the next sequence.
Kotlin compiles to Java, which has both, a primitive type long and a Class Long
As per the Java Persistence Specification in section 11.1.21 Id Annotation both can be used for the Id:
The field or property to which the Id annotation is applied should be one
of the following types: any
Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date;
java.sql.Date; java.math.BigDecimal; java.math.BigInteger[109].
There is an advantage in using the Class over the primitive, as null has a more unambiguous meaning. But from the spec both are possible and you have to decide weather you favor Kotlins nullsafety over the the jpa style or the other way around.
Usually, data class is useful to ruturn more that one result from a method , but not for entities (just my opinion).
Sometimes it is not a good idea to set a default value for the id field.
After some time experimenting with Kotlin and entities (actually, with documents for MongoDb, but anyway it has id),
Looks like the better way is to use lateinit var. You can create the top class of entity hierarchy:
open class Identifiable {
lateinit var id: Long // or String or UUID
//explicitly define equals & hash code here
}
But be careful, for equals, hashcode and if you want to provide toString method in heirs, then it is a good idea to provide extra nullable field, something like:
open class Identifiable {
lateinit var id: Long // or String or UUID
val nullableId: Long?
get() {
return if(this::id.isInitialized) id else null
}
//explicitly define equals & hash code here with nullableId
}
class User {
override fun toString() = "User(id=${nullableId})"
}
In this case, you will avoid an exception when you will try to log your created but not saved in DB entity

mapStruct: map list to other list?

I have a list List<Payment> which I'd like to map to another list List<PaymentPlan>. These types look like this:
public class Payment {
#XmlElement(name = "Installment")
#JsonProperty("Installment")
private List<Installment> installments = new ArrayList<>();
#XmlElement(name = "OriginalAmount")
#JsonProperty("OriginalAmount")
private BigDecimal originalAmount;
//getters setters, more attributes
}
and....
public class PaymentPlan {
//(Installment in different package)
private List<Installment> installments;
#XmlElement(name = "OriginalAmount")
#JsonProperty("OriginalAmount")
private BigDecimal originalAmount;
//getters setters, more attributes
}
I expect that something like this is working...
#Mappings({
#Mapping(//other mappings...),
#Mapping(source = "payments", target = "paymentInformation.paymentPlans")
})
ResultResponse originalResponseToResultResponse(OrigResponse originalResponse);
...but I get:
Can't map property java.util.List<Payment> to java.util.List<PaymentPlan>.
Consider to declare/implement a mapping method java.util.List<PaymentPlan> map(java.util.List<Payment> value);
I don't know how to apply this information. First I though I need to declare some extra mapping (in the same mapper class) for the lists, so MapStruct knows how to map each field of the List types like this:
#Mappings({
#Mapping(source = "payment.originalAmount", target = "paymentInformation.paymentPlan.originalAmount")
})
List<PaymentPlan> paymentToPaymentPlan(List<Payment> payment);
...but I get error messages like
The type of parameter "payment" has no property named "originalAmount".
Obviously I do something completely wrong, since it sound like it does not even recognize the types of the List.
How can I basically map from one List to another similar List? Obviously I somehow need to combine different mapping strategies.
btw: I know how to do it with expression mapping, like...
#Mapping(target = "paymentPlans",expression="java(Helper.mapManually(payments))")
but I guess MapStruct can handle this by iself.
I presume you are using version 1.1.0.Final. Your extra mapping is correct, the only difference is that you need to define a mapping without the lists MapStruct will then use that to do the mapping (the example message is a bit misleading for collections).
PaymentPlan paymentToPaymentPlan(Payment payment);
You don't even need the #Mappings as they would be automatically mapped. You might also need to define methods for the Instalment (as they are in different packages).
If you switch to 1.2.0.CR2 then MapStruct can automatically generate the methods for you.

kotlin data class + bean validation jsr 303

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets

GWT AutoBean - not serializing ArrayList<String>, other pojos ok?

I can't get an ArrayList to serialize using the AutoBean mechanism. I'm using GWT 2.7. Here is my setup:
public interface IUser {
String getName();
void setName(String name);
ArrayList<String> getFriends();
void setFriends(ArrayList<String> friends);
}
public class User implements IUser {
private String name;
private ArrayList<String> friends;
... getters and setters for all members attributes ...
}
then my bean factory:
public interface AutoBeanFactoryImpl extends AutoBeanFactory {
AutoBean<IUser> user(IUser inst);
}
and finally my usage:
ArrayList<String> friends = new ArrayList<String>();
friends.add("Mary");
User user = new User();
user.setName("Fritz");
user.setFriends(friends);
AutoBeanFactoryImpl factory = GWT.create(AutoBeanFactoryImpl.class);
AutoBean<IUser> bean = factory.user(user);
String json = AutoBeanCodex.encode(bean).getPayload();
The output json does not have the friends array. It has the name ok though. Why doesn't the string array get serialized? Do I need something special for that?
Thanks
The problem is that you specified ArrayList instead of just plain List - in order to nicely generate code that fills in the gaps between Java and JSON, autobeans only want to work with interfaces. Aside from getters and setters, AutoBeans have special support for List, Set (which is just a List without duplicates in the JSON), and Map. If you specify a particular implementation, the AutoBean tooling can't always handle it.
Feel free to pass in an ArrayList instance, but declare your getter and setter to be of type List.
As an aside, those collections can be of any type that AutoBeans can otherwise handle - including numbers, booleans, strings, dates, and any other bean-like interface that also conform to what AutoBeans can deal with.

A strange phenomenon when use dozer in jpa project,why Mapping annotation in lazy load object can't work?

I met a very strange phenomenon when using dozer in jpa project.
I have a UserSupplier object and a Supplier object.
UserSupplier:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "supplier_id", nullable = false)
private Supplier supplier;
In my code I first query a UserSupplier List, then convert it to SupplierList.
List<Supplier> supplierList = new ArrayList<>(usList.size());
usList.forEach(us -> supplierList.add(us.getSupplier()));
Then I convert SupplierList to SupplierView List and return it to Caller.
BeanMapper.mapList(supplierList, SupplierView.class);
My dozer configure in these objects like below
Supplier:
#Id
#GeneratedValue
#Mapping("supplierId")
private int id;
SupplierView:
private int supplierId;
Very funny, supplierId in SupplierView always 0(default int value),but other fileds can convert successfully, only id field fail. I don't why is this, why only id field can't convert to supplierId, but other fields could?
For above problem, there are below solutions
1. Change field name (supplierId to id):
Supplier:
// #Mapping("supplierId")
private int id;
SupplierView:
private int id;
but Caller(front-end) have to change code.
2. Change fetchType to eager:
UserSupplier:
#ManyToOne
private Supplier supplier;
After reading dozer documentation, I find some thing. After trying it, I got another solution.
That is add a dozer.properties into classpath, content inside is
org.dozer.util.DozerProxyResolver=org.dozer.util.HibernateProxyResolver
More detail please see
http://dozer.sourceforge.net/documentation/proxyhandling.html
This is probably because JPA uses proxy objects for lazy loading of single entity reference. Proxy object is effectively a subclass of your entity class. I guess that dozer can find #Mapping annotation only on fields declared in the class of given object, and not on fields defined in parent classes. Dozer project states that annotation mapping is experimental. Therefore it is possible that it does not cover mapping class hierarchies well.
I suggest to try configure mapping of supplierId by other means (XML, dozer mapping API) and see if it works. If all fails, you could write a custom MapperAware converter between Supplier and SupplierView. You would map source object to target object using supplied mapper, and finilize it by copying value of id to supplierId.