Here's an example to give users an idea of what it looks like:
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.ignoringFields("name", "age")
.forcingRecursiveComparisonFor(Child.class, Sister.class)
.isEqualTo(expectedPerson);
This will be in Beta as it might change according to people's feedback.
Implements an easy to use/discover API as in:
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.ignoringFields("name", "age")
.isEqualTo(expectedPerson);
Users can specify the comparison to ignore all null fields in the object under test.
Users can specify to ignore specific fields in the object under test:
person.sister.name.*name means name, person.name and person.sister.name are ignored.Note if you ignore specific field, all sub fields are ignored too, for example if person is ignored then person.name, person.address, person.address.number are ignored too.
Nice to have: report all the fields ignored in the comparison
equals method or notBy default the recursive comparison will use equals if it had been overridden but users should be able to specify to ignore overridden equals methods and compare objects field by field.
This can be done:
forcingRecursiveComparisonFor(Child.class, Sister.class)forcingRecursiveComparisonForFields("child", "sister") Nice to haveExample:
assertThat(person).usingRecursiveComparison()
.forcingRecursiveComparisonFor(Child.class, Sister.class)
.forcingRecursiveComparisonForFields("name", "city")
.isEqualTo(expectedPerson);
equals method for types matching given regexesequals method for given typesequals method for given fieldsequals method for all fieldsUsers should be able to specify a comparator for a given type or fields:
usingComparatorForType(String.class, caseInsensitiveComparator) for all types usingComparatorForFields(caseInsensitiveComparator, "name", "city") a specific given list of fields (vararg)To keep the API simple once a comparator for a given type is registered it should be used at any level, collection element or collection element field.
Field comparators take precedence over type ones as they are more fine grained.
Once a comparator is registered for a type or a field, it replaces the recursive comparison when these types/fields are being compared.
This will specify if two instances with the same fields but from different classes are considered equals, it allows for example to compare a Person with a PersonDto. If the check is strict the expected object class must be compatible (i.e. extends) actuals, so if Employee inherits Person one can compare a person with an employee (but not an employee with a person. The check is performed on the root objects and their fields.
By default the check is lenient but it can be made strict to fail the comparison.
TreeSet vs HashSet).The recursive comparison should handle cycles, for example when a -> b -> c -> a.
Maps entries should be considered as fields.
See if it is possible and relevant to report all compared maps internal differences (size, missing elements, elements differences, order when order is consistent ...) instead just reporting that the maps differ.
On hold at the moment as it is not crystal what can be achieved reasonably.
At the moment assertThat(Iterable/array) does not expose usingRecursiveComparison() which makes it cumbersome to test them field/field.
The comparison by default fails if we compare an ordered collection to an unordered one but this should be configurable
Nice to have: ignores order in comparison (by default elements are compared in order for ordered collection).
Nice to have: allow to compare ordered collections with unordered one. (this should be disabled by default)
Nice to have: allow to compare array with ordered collections like list. (this should be disabled by default)
Example:
assertThat(fellowshipOfTheRing).usingRecursiveComparison()
.contains(frodo, sam, merry, pippin, gandalf,
legolas, boromir, aragorn, gimli);
A failure must report the recursive comparison specification:
equals are used and not usedThe failure should show the number of differences and describe them all.
A difference describes fields by their full path from the root object, the actual value, the expected one and the comparison used if the user specified one.
Values are represented with AssertJ standard representation or the one set by the user.
Actual and expected values type should be displayed when strict type checking is enable.
The differences reported must be ordered by path alphabetically.
Difference report example:
person.sister.name differs:
- actual value : "Sansa"
- expected value: "Arya"
- comparison was performed with caseInsensitiveComparator
The path to field must support maps and adress https://github.com/joel-costigliola/assertj-core/issues/1303.
To avoid repeating the same recursive configuration before each assertion, AssertJ should provide:
AssertJ will extend the mechanism used to configure Representation.
If users don't want to change the default behavior globally but on a smaller scope, it will possible to capture a recursive comparison specification and reuse it.
Example:
// default behavior
RecursiveComparisonSpecification recursiveComparisonSpecification = new RecursiveComparisonSpecification();
recursiveComparisonSpecification.ignoreNullFields();
recursiveComparisonSpecification.forceRecursiveComparisonFor(Child.class, Sister.class);
assertThat(person).usingRecursiveComparison(recursiveComparisonSpecification)
.isEqualTo(expectedPerson);
assertThat(person2).usingRecursiveComparison(recursiveComparisonSpecification)
.isEqualTo(expectedPerson2);
The example above is equivalent to this where we have to repeat call to ignoringNullFields and forcingRecursiveComparisonFor for each assertions:
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.forcingRecursiveComparisonFor(Child.class, Sister.class)
.isEqualTo(expectedPerson);
assertThat(person2).usingRecursiveComparison(recursiveComparisonSpecification)
.ignoringNullFields()
.forcingRecursiveComparisonFor(Child.class, Sister.class)
.isEqualTo(expectedPerson2);
The recursive comparison specification must be transferred after calling methods that change the object under test: extracting, asString ...
The current way comparing objects recursively is starting to pollute the api, the methods introduced for the recursive comparison don't apply for the other assertion cluttering the assertj api, ex usingComparatorForType.
I would like to introduce a separate API to use recursive comparison allowing to fine tune the comparison behavior.
import static RecursiveComparisonSpecification;
// ignoringNullFields() comes from RecursiveComparisonSpecification
assertThat(person).usingRecursiveComparison(ignoringNullFields()
.ignoringFields("name", "age")
.forcingRecursiveComparisonFor(Child.class, Sister.class))
.isEqualTo(expectedPersont)
Another option would be to let usingRecursiveComparison return a special Assert class (e.g. RecursiveComparisonAssert), so you could write
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.ignoringFields("name", "age")
.forcingRecursiveComparisonFor(Child.class, Sister.class)
.isEqualTo(expectedPersont);
I do like the design! But there is something that, I believe, is misleading (actually it is already the case with current version of AssertJ): usingRecursiveComparison expression does not say anything about the fact that the recursive comparator will not be used on attributes having a type overidding equals. Of course, it becomes clear if we introduce a forcingRecursiveComparisonFor method, but all users won't see it at first glance.
So, it would be nice to have a way to clarify that for users, either by changing usingRecursiveComparison to something else, either by forcing users to call a specific method in order to get a RecursiveComparisonSpecification instance. Going a step forward: maybe that specific method could be used to specify if the recursive comparison should be forced on any type ?
Here is my proposal:
import static RecursiveComparisonSpecificationFactory;
// forcingRecursiveComparisonForAll() comes from RecursiveComparisonSpecificationFactory
assertThat(person).usingRecursiveComparison(forcingRecursiveComparisonForAll()
.ignoringFields("name", "age"))
.isEqualTo(expectedPerson)
// forcingRecursiveComparisonFor() comes from RecursiveComparisonSpecificationFactory
assertThat(person).usingRecursiveComparison(forcingRecursiveComparisonFor(Child.class, Sister.class)
.ignoringFields("name", "age"))
.isEqualTo(expectedPerson)
You can only get a RecursiveComparisonSpecification instance by calling one of two methods on RecursiveComparisonSpecificationFactory:
forcingRecursiveComparisonForforcingRecursiveComparisonForAllThis way it will be clear for users how the comparison will actually be performed. What do you think ?
@joel-costigliola usingComparatorForType and usingComparatorForFields are also used by the non-recursive field/property by field/property comparison methods (isEqualToComparingFieldByField, isEqualToIgnoringNullFields, isEqualToComparingOnlyGivenFields and isEqualToIgnoringGivenFields). In order to remove them from ObjectAssert we would have to add a similar API for non-recursive comparison.
@joel-costigliola I think you are right. Recursive comparison is one of the best feature of AssertJ and having a dedicated api make sense.
It would be nice to be able to explain the way recursive comparison should work for some attributes (for a given depth).
Maybe give a list of rules for each of those specials attributes could be a way to do so ?
thank you all for your feedback, the api and its behaviour are still opened to discussion and changes, let's continue brainstorming it.
I wonder if the field by field comparison should be the default even for classes having overriding equals, I'd like to have your opinion on this one guys. In that case we could add a useOverriddenEquals to change that maybe with whitelist/blacklist (if that's not much).
I like @PascalSchumacher suggestion, it will make the API easier to discover, we will go that way except if we encounter technical limitations.
@pierrefevrier can you elaborate ? do you refer to add information in the error message explaining how the comparison was performed and (obviously) what went wrong ? (yes we will do this).
I think the custom equals of classes should be used by default, because otherwise it is a pain to compare two objects e.g. containing a collection object.
Hey,
what do you think about the possibility to decide whether field-by-field comparison or comparison using equals() for certain types or attributes should be used?
For types one could specify the classes similar to @PascalSchumacher's example:
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.ignoringFields("name", "age")
.forcingRecursiveFieldByFieldComparisonFor(Child.class, Sister.class)
.forcingRecursiveEqualityComparisonFor(Mother.class, Father.class)
.isEqualTo(expectedPersont);
For attributes the information in which type the attribute is located might be useful.
What about something like of() or memberOf():
assertThat(person).usingRecursiveComparison()
.ignoringNullFields()
.ignoringFields("name", "age")
// of() and in() are aliases for memberOf()
.forcingRecursiveFieldByFieldComparisonForField("sister").of(Person.class)
.forcingRecursiveEqualityComparisonForField("child").memberOf(Father.class)
.forcingRecursiveEqualityComparisonForField("mother").in(Child.class)
.isEqualTo(expectedPersont);
This would result in the equality-comparison being used for the child attribute of class Father, but not for class Mother.
I think sth. in this direction could work out, but I surely haven't thought through it completely.
What's your opinion? Do you see pitfalls?
As explained in #1054 :
when using isEqualToComparingFieldByFieldRecursively or isEqualToComparingFieldByField, the comparison is not done on class type, only on field value (and name).
So two POJO with the same fields, but from different classes are considered as equals.
It could be useful to test the object instance (recursively, or not)
Example
```
@Test
public void test() {
A a = new A(10);
B b = new B(10);
assertThat(a).isEqualToComparingFieldByFieldRecursively(b);
// I would like this line to fail because A and B are different classes
// but it is not failing
}
private static class A {
int x;
public A(int x) {
this.x = x;
}
}
private static class B {
int x;
public B(int x) {
this.x = x;
}
}
Same too when the object contains some other object :
private static class Something {
InnerObject inner; // can be A or B
public Something(InnerObject inner) {
this.inner = inner;
}
}
Something someObjectContainingA = new Something(new A(10));
Something someObjectContainingB = new Something(new B(10));
assertThat(someObjectContainingA).isEqualToComparingFieldByFieldRecursively(someObjectContainingB);
```
the 2 object have the same class, but they are composed of different object, and it could be interesting to check this field type.
Thanks for the great library.
I think I can also add an edge case: Shadowing variables.
public class ShadowedValuesAreComparedFlat {
public class Super {
private int x;
public Super(int x) {
this.x = x;
}
}
public class Sub extends Super {
private int x;
public Sub(int superValue, int subValue) {
super(superValue);
this.x = subValue;
}
}
@Test
public void shadowed_values_are_not_considered() {
Super instanceSuper = new Super(10);
Sub instanceSub = new Sub(20, 10);
// Passes the test: Should it fail instead?
assertThat(instanceSub).isEqualToComparingFieldByFieldRecursively(instanceSuper);
}
}
here Sub contains two values of x on two different levels.
I think the current implementation takes the first one it finds on each and compares them. So it ends up comparing super.x with sub.x, which are both 10.
I am not quite sure what the expectation would be. It depends if one wants to compare super with subclasses at all and if yes, what should be the policy to do it.
Status Quo in assertj-core 3.8.0 for the above is: the test passes.
As this ticket is about redesigning the recursive comparison, I think this would be the right place to make you aware and possible discuss, what should be the output.
That's really an edge case ! Thanks for reporting it.
The philosophy of the recursive is to provide a data comparison of actual and expected but should it look at the data values only or also at the data graph structure (i.e. where do the data values come from) ?
In the "the data values only" case, the assertion should pass because if you ask the subclass instance for its x value it will always return its own and not super.x so from this point of view super.xis like it does not exist.
If we want to consider the data structure then the assertion should fail because Sub has two fields whereas Super only one. We could also go further and only compare instances of the same class only.
I'm not overly opinionated on this but I think that we should go data values only and make sure we get the values from the subclasses field first (which we do), I also think that edge case comes from a bad coding practice (why anyone would shadow a parent field ?) and thus I don't think we should cater for it (I'm happy to change my mind if you can show me a relevant use case for shadowing fields).
I agree that this is both an edge case and likely bad practice.
My intention was to make you aware of it, to make an aware decision for the new API. Depending on the definition of equals in a class doing this, it could or could not be consistent with what isEqualToComparingFieldByFieldRecursively is doing.
We could also go further and only compare instances of the same class only.
If you go down this path, this becomes a non-issue. However in a previous issue I saw you opting for not walking that path, though I am not sure if this holds true for the new API as well?
In the case where subclassing is allowed, one needs a policy for dealing with this.
I also have no strong opinion about it. Normally I would go for "do not consider different classes as equal" to begin with. That that's a strong opinionated approach which is maybe not suitable for a library which wants to support different developer.
My gut feeling is that I would not support this and shy away from the effort of making a path dependent aware equality check and would just support the "no subclassing at all" and "subclassing without shadowing" cases.
However I think it might be worth documenting that this case is not supported.
I'm not keen of verifying instances are of the same class since it is something that users can do easily if they want to before performing the recursive comparison.
As you suggested, the behavior should be clearly documented.
Requirement:
To keep the API simple once a comparator for a given type is registered it should be used at any level, collection element or collection element field. At the moment we can register comparators that are only applied at the collection element field level and others that are applied at both element and element's field levels - too complicated !
Requirement:
Come with good description to clearly state which comparators were used.
isDeepEqualTo() is what I was looking for in AssertJ when I arrived at #1002.
I think I'll stick with DeepEquals from https://github.com/jdereg/java-util
Or the technique I've been using for fifteen years via XStream:
assertEqual(xstream.toXML(expected), xstream.fromXML(actual));
Thank you for your great work. I've just tested the current 3.12.0-SNAPSHOT version but it looks like there's a small issue. As already mentioned in 1410 I'm having the situation that a field exists only at one side. So I'd like to exclude this field from comparision by using 'ignoringFields'. I'm using the follwoing command:
assertThat(entity).usingRecursiveComparison().ignoringFields("mySpecialField").isEqualTo(document);
But it gives me the following error:
java.lang.AssertionError:
Expecting:
<A@2ce80c85>
to be equal to:
<B@6c758c74>
when recursively comparing field by field, but found the following difference:
field/property '' differ:
- actual value : A@2ce80c85
- expected value : B@6c758c74
A can't be compared to B as B does not declare all Entity fields, it lacks these:[mySpecialField]
The recursive comparison was performed with this configuration:
- the following fields were ignored in the comparison: id, mySpecialField
- overridden equals methods were used in the comparison
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
For me it looks like that in RecursiveComparisonDifferenceCalculator#determineDifferences in the block if (!key2FieldsNames.containsAll(key1FieldsNames)) {
}
after the line key1FieldsNamesNotInKey2.removeAll(key2FieldsNames); the values from ignoringFields(...) must not be contained in missingFields.
What do yo think about that?
It looks like you are right, I'll look into it tonight and push a fix for it (at least the error message was helpful!).
Thanks for the feedback and the investigation :+1:.
@merlin-hst I have (force) pushed the branch with a fix for #1410 if you want to give it a try.
Now I'm getting the follwing error:
java.lang.AssertionError:
Expecting:
<AEntity [id=6]>
to be equal to:
<ADocument@729452f2>
when recursively comparing field by field, but found the following 2 differences:
field/property 'details' differ:
- actual value : [ADetailEntity@6bb01b34, ADetailEntity@374bbe80]
- expected value : [ADetailDocument@381c78ea, ADetailDocument@75cb2090]
field/property 'eventContent' differ:
- actual value : AContentEntity@156aba9a
- expected value : AContentDocument@45dac901
The recursive comparison was performed with this configuration:
- the following fields were ignored in the comparison: id, isMarkedForTermination
- overridden equals methods were used in the comparison
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
Based on that message it looks like that for associated objects the types are compared. But I didn't take a look into the code.
@merlin-hst By default overridden equals are used to compare instances, this can be disabled by fields or type at the moment with ignoringOverriddenEqualsForXXX see https://github.com/joel-costigliola/assertj-core/blob/recursive-api/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java#L487.
I probably should add an ignoringOverriddenEquals() to force recursive comparison for all types (except java lang ones).
@merlin-hst By default overridden equals are used to compare instances, this can be disabled by fields or type at the moment with ignoringOverriddenEqualsForXXX see https://github.com/joel-costigliola/assertj-core/blob/recursive-api/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java#L487.
I probably should add an ignoringOverriddenEquals() to force recursive comparison for all types (except java lang ones).
I've seen that. But I don't have overriden equals. Nevertheless having ignoringOverriddenEquals() seems to be helpfull.
Interesting, would you be able to provide a test to reproduce this ?
I'll try it. If it helps my objects look like:
class AEntity extends AbstractEntity{
private Set<ADetailEntity> details;
private AContentEntity content;
//....
}
class ADocument extends BaseDocument {
private Set<ADetailDocument> details;
private AContentDocument content;
//...
}
None of them have equals ovverriden. Even not the base classes.
I'm keen to see the more details on the classes you deal with, I have this test passing which compares different types and no equals method:
@Test
public void should_pass_by_default_when_objects_data_are_equals_whatever_their_types_are() {
// GIVEN
Person actual = new Person("John");
actual.home.address.number = 1;
actual.dateOfBirth = new Date(123);
actual.neighbour = new Person("Jack");
actual.neighbour.home.address.number = 123;
actual.neighbour.neighbour = new Person("James");
actual.neighbour.neighbour.home.address.number = 124;
PersonDto expected = new PersonDto("John");
expected.home.address.number = 1;
expected.dateOfBirth = new Date(123);
expected.neighbour = new PersonDto("Jack");
expected.neighbour.home.address.number = 123;
expected.neighbour.neighbour = new PersonDto("James");
expected.neighbour.neighbour.home.address.number = 124;
// THEN
assertThat(actual).usingRecursiveComparison()
.isEqualTo(expected);
}
Taking a quick look into the test class and the data objects it seems to me that actual.home and expected.home are of the same type. In my situation I'd have also a HomeDto and so on. So for every "given" type a similiar Dto type is used.
^^ I actually have updated the test to have Dto versions of all types (HomeDto, AddressDto) and it worked fine (I haven't pushed that commit yet though).
I've made a simple demo to reproduce the mentioned error. If you're interested in I can provide it as maven project.
But in the meantime it's getting more cleaner to me:
1: There were a difference I was not aware of.
I've just found another issue (?):
When I try a pattern like ignoringFieldsByRegexes(".*creation[\\D]+", ".*update[\\D]+") I'm getting a StackOverflowError.
java.lang.StackOverflowError
at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3797)
at java.util.regex.Pattern$Curly.match(Pattern.java:4227)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
at java.util.regex.Pattern$Branch.match(Pattern.java:4604)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4568)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)
at java.util.regex.Pattern$Curly.match0(Pattern.java:4279)
at java.util.regex.Pattern$Curly.match(Pattern.java:4234)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)
at java.util.regex.Pattern$Branch.match(Pattern.java:4604)
at java.util.regex.Pattern$Branch.match(Pattern.java:4602)
at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3798)
at java.util.regex.Pattern$Start.match(Pattern.java:3461)
at java.util.regex.Matcher.search(Matcher.java:1248)
at java.util.regex.Matcher.find(Matcher.java:664)
at java.util.Formatter.parse(Formatter.java:2549)
at java.util.Formatter.format(Formatter.java:2501)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration.concatenatedPath(RecursiveComparisonConfiguration.java:253)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration.shouldKeepField(RecursiveComparisonConfiguration.java:247)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration.lambda$shouldKeepField$0(RecursiveComparisonConfiguration.java:243)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
at java.util.Iterator.forEachRemaining(Iterator.java:116)
at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.getNonIgnoredFieldNames(RecursiveComparisonDifferenceCalculator.java:318)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.initStack(RecursiveComparisonDifferenceCalculator.java:288)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.determineDifferences(RecursiveComparisonDifferenceCalculator.java:91)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.compareUnorderedCollection(RecursiveComparisonDifferenceCalculator.java:452)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.determineDifferences(RecursiveComparisonDifferenceCalculator.java:196)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.compareUnorderedCollection(RecursiveComparisonDifferenceCalculator.java:452)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.determineDifferences(RecursiveComparisonDifferenceCalculator.java:196)
at org.assertj.core.api.recursive.comparison.RecursiveComparisonDifferenceCalculator.compareUnorderedCollection(RecursiveComparisonDifferenceCalculator.java:452)
...
By using a pattern like ignoringFieldsByRegexes("creation[\\D]+", "update[\\D]+") (without the .* at the beginning) I'm getting the previously mentioned AssertionError again.
Hoe that helps
@merlin-hst a small project would be great or if it is simpler just create a PR on top of this branch.
I'll look into the stack overflow error, thanks again for you feedback.
@merlin-hst I have pushed a fix for the recursive error, the test you gave me fails with the error below.
I'd like to have an option to ignore fields / regex in all (nested / associated) objects regardless of the path.
Isn't that achievable with regexes ?
Improvement of the output. In the debugger I've seen that the "ComparisonDifference" object contains detailed informaion about which fields (of an nested / associated object) differ. But these information are not (fully ?) printed out in an AssertionError (".. it lacks these ....").
I did not understand this one, can you clarify with an example ?
java.lang.AssertionError:
Expecting:
<AEntity [id=1887776889599131648]>
to be equal to:
<de.demo.assertj.recursivetest.model.ADocument@731a74c>
when recursively comparing field by field, but found the following 2 differences:
field/property 'content' differ:
- actual value : de.demo.assertj.recursivetest.model.AContentEntity@3327bd23
- expected value : de.demo.assertj.recursivetest.model.AContentDocument@210366b4
de.demo.assertj.recursivetest.model.AContentEntity can't be compared to de.demo.assertj.recursivetest.model.AContentDocument as AContentDocument does not declare all AContentEntity fields, it lacks these:[id, event]
field/property 'details' differ:
- actual value : [de.demo.assertj.recursivetest.model.ADetailEntity@31dc339b]
- expected value : [de.demo.assertj.recursivetest.model.ADetailDocument@66480dd7]
The recursive comparison was performed with this configuration:
- the following fields were ignored in the comparison: id, isMarkedForTermination, isTerminated, creationDate, creationUser, updateDate, updateUser
- the fields matching the following regexes were ignored in the comparison: .*creation[\D]+, .*update[\D]+
- overridden equals methods were used in the comparison
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
I'll provide you a working version.
But as I already mentioned in my opinion the error message is a little bit to abstract. It indicates that the fields 'content' and 'details' of AEntity and ADocument differs. But because both fields are complex objects I'd like to know why they differ. When I inspect the ComparisonDifference objects in the debugger I can see that there is a field called 'additionalInformation' with some more information about the difference, for example what exactly is missing (AContentDocument does not declare all AContentEntity fields, it lacks these:[id]]). So for me it looks like that the information of these field should be available too in the AssertError message.
Second using 'ignoringFieldsMatchingRegexes' could be sometimes too much. When I'd like to ignore a field called 'id' regardless of the path I can use '.*id' as pattern. But that would match all fields ending with 'id' which might not what I wanted. So from my point of view an option to ignoring specific fields regarding of the path could be very convenient.
Hi,
I see that there is a case for ignoring order for arrays and it has been already implemented
(Iterable/array support section - ignores order in comparison (by default elements are compared in order for ordered collection).)
I have one more example)
Let's consider the following Objects
data class AccountServiceFeaturesMetaInfo(
var serviceFeatures: List<ServiceFeatureMetainfo?>?,
...
)
data class ServiceFeatureMetainfo(
var hasParent: Int?,
var flagValue: String?,
var attributes: List<Attribute?>?
...
)
1) I need to ignore one field for recursive comparison for array of ServiceFeatureMetainfo, e.g. hasParent
2) How to ignore order for attributes array
3) How to ignore some fields for several child arrays, e.g. some field of Attribute object
It would be great to implement something like this:
assertThat(response)
.usingRecursiveComparison()
.ignoringFields("serviceFeatures[].hasParent")
.ignoringFields("serviceFeatures[].attributes[].gui")
.ignoreOrderFor("serviceFeatures[].attributes[]")
Also, I noticed that assert error message for complex objects is not precise. If you have only one difference in child of childs of the object you get the first level child in the assert error message. It's too hard to find where is the error
field/property 'serviceFeatures' differ:
- actual value : ServiceFeatureMetainfo(hasParent=1, flagValue=~, defined=null, name=conf_enabled, staticInheritanceFlag=null, parentFeature=ParentFeature(entityName=[baseAddon], entity=Product, flagValue=Y, name=conf_enabled, attributes=[Attribute(multivalue=0, gui=null, name=max_participants, values=32)],...
- expected value : ServiceFeatureMetainfo(hasParent=1, flagValue=~, defined=null, name=conf_enabled, staticInheritanceFlag=null, parentFeature=ParentFeature(entityName=[baseAddon], entity=Product, flagValue=Y, name=conf_enabled, attributes=[Attribute(multivalue=0, gui=null, name=max_participants, values=33)],...
Hello, first of all amazing framework !
I have two questions :
I'd like to know if it is possible, when asking for a strict type checking, to discard it for lambda expressions ?
Also is there a way to provide a custom comparator for some fields matching a regex ? The regex builder for discarding some fields is very useful as I am now dealing with a code that has some cyclical pointing in it, but I would need the same kind of logic for applying a custom comparator to those fields. Is there already a way to do so please ? Currently I have a work around with just discarding them in a first place and then looking for them explicitly but this is hardly extensible.
Thank you very much !
hey thanks for the kind words!
I'd like to know if it is possible, when asking for a strict type checking, to discard it for lambda expressions ?
it's not supported at the moment, can you give us a concrete use case? (with an example if possible)
create a new issue for it, it will be easier to discuss about it.
Also is there a way to provide a custom comparator for some fields matching a regex ?
Not supported, but we can add that, same as my other comment, create a new issue for it and provide a concrete example (will be helpful for testing)
closing this as the recursive comparison has reached a decent state
Most helpful comment
Another option would be to let
usingRecursiveComparisonreturn a special Assert class (e.g. RecursiveComparisonAssert), so you could write