Skip to content

Commit 3e2f8e8

Browse files
Add ignoringAllOverriddenEquals
Refactor tests
1 parent b080f3e commit 3e2f8e8

13 files changed

+263
-75
lines changed

src/main/java/org/assertj/core/api/recursive/comparison/DualKey.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,8 @@ public List<String> getPath() {
6060
public String getConcatenatedPath() {
6161
return concatenatedPath;
6262
}
63+
64+
public boolean isBasicType() {
65+
return key1.getClass().getName().startsWith("java.lang");
66+
}
6367
}

src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static org.assertj.core.error.ShouldBeEqualByComparingFieldByFieldRecursively.shouldBeEqualByComparingFieldByFieldRecursively;
1717

1818
import java.util.Comparator;
19+
import java.util.Date;
1920
import java.util.List;
2021
import java.util.Map;
2122
import java.util.stream.Stream;
@@ -316,6 +317,66 @@ public RecursiveComparisonAssert ignoringFieldsByRegexes(String... regexes) {
316317
return this;
317318
}
318319

320+
/**
321+
* By default the recursive comparison uses overridden {@code equals} methods to compare fields,
322+
* this method allows to force a recursive comparison for all fields at the exception of basic types (i.e. java.lang types)
323+
* - at some point we need to compare something!
324+
* <p>
325+
* For the recursive comparison to use the overridden {@code equals} of a given type anyway (like {@link Date}) you can register
326+
* a type comparator using {@link #withComparatorForType(Class, Comparator)}.
327+
* <p>
328+
* TODO introduce {@code ignoringAllOverriddenEqualsExceptFor(Class<?>... classes)} ?
329+
* <p>
330+
* Example:
331+
* <pre><code class='java'> public class Person {
332+
* String name;
333+
* double height;
334+
* Home home = new Home();
335+
* }
336+
*
337+
* public class Home {
338+
* Address address = new Address();
339+
* }
340+
*
341+
* public static class Address {
342+
* int number;
343+
* String street;
344+
*
345+
* // only compares number, ouch!
346+
* {@literal @}Override
347+
* public boolean equals(final Object other) {
348+
* if (!(other instanceof Address)) return false;
349+
* Address castOther = (Address) other;
350+
* return Objects.equals(number, castOther.number);
351+
* }
352+
* }
353+
*
354+
* Person sherlock = new Person("Sherlock", 1.80);
355+
* sherlock.home.address.street = "Baker Street";
356+
* sherlock.home.address.number = 221;
357+
*
358+
* Person sherlock2 = new Person("Sherlock", 1.80);
359+
* sherlock2.home.address.street = "Butcher Street";
360+
* sherlock2.home.address.number = 221;
361+
*
362+
* // assertion succeeds but that's not what we expected since the home.address.street fields differ
363+
* // but the equals implemenation in Address does not compare them.
364+
* assertThat(sherlock).usingRecursiveComparison()
365+
* .isEqualTo(sherlock2);
366+
*
367+
* // to avoid the previous issue, we force a recursive comparison on the home.address field
368+
* // now this assertion fails as we expect since the home.address.street fields differ
369+
* assertThat(sherlock).usingRecursiveComparison()
370+
* .ignoringAllOverriddenEquals()
371+
* .isEqualTo(sherlock2);</code></pre>
372+
*
373+
* @return this {@link RecursiveComparisonAssert} to chain other methods.
374+
*/
375+
public RecursiveComparisonAssert ignoringAllOverriddenEquals() {
376+
recursiveComparisonConfiguration.ignoreAllOverriddenEquals();
377+
return this;
378+
}
379+
319380
/**
320381
* By default the recursive comparison uses overridden {@code equals} methods to compare fields,
321382
* this method allows to force a recursive comparison for the given fields (it adds them to the already registered ones).

src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class RecursiveComparisonConfiguration {
5050
private List<Class<?>> ignoredOverriddenEqualsForTypes = new ArrayList<>();
5151
private List<FieldLocation> ignoredOverriddenEqualsForFields = new ArrayList<>();
5252
private List<Pattern> ignoredOverriddenEqualsRegexes = new ArrayList<>();
53+
private boolean ignoreAllOverriddenEquals = false;
5354

5455
// registered comparators section
5556
private TypeComparators typeComparators = defaultTypeComparators();
@@ -254,7 +255,10 @@ private static String concatenatedPath(String parentPath, String name) {
254255
}
255256

256257
boolean shouldIgnoreOverriddenEqualsOf(DualKey dualKey) {
257-
return matchesAnIgnoredOverriddenEqualsField(dualKey) || shouldIgnoreOverriddenEqualsOf(dualKey.key1.getClass());
258+
if (dualKey.isBasicType()) return false; // we must compare basic types otherwise the recursive comparison loops infinitely!
259+
return ignoreAllOverriddenEquals
260+
|| matchesAnIgnoredOverriddenEqualsField(dualKey)
261+
|| shouldIgnoreOverriddenEqualsOf(dualKey.key1.getClass());
258262
}
259263

260264
@VisibleForTesting
@@ -415,4 +419,8 @@ private void describeTypeCheckingStrictness(StringBuilder description) {
415419
description.append(format(str));
416420
}
417421

422+
public void ignoreAllOverriddenEquals() {
423+
ignoreAllOverriddenEquals = true;
424+
}
425+
418426
}

src/main/java/org/assertj/core/util/Lists.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static <T> ArrayList<T> newArrayList(T... elements) {
4545
}
4646

4747
@SafeVarargs
48-
public static <T> ArrayList<T> list(T... elements) {
48+
public static <T> List<T> list(T... elements) {
4949
return newArrayList(elements);
5050
}
5151

src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_Test.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static org.assertj.core.test.NeverEqualComparator.NEVER_EQUALS;
2929
import static org.assertj.core.test.TestFailures.failBecauseExpectedAssertionErrorWasNotThrown;
3030
import static org.assertj.core.util.Lists.list;
31+
import static org.junit.jupiter.params.provider.Arguments.arguments;
3132
import static org.mockito.Mockito.verify;
3233

3334
import java.sql.Timestamp;
@@ -117,10 +118,10 @@ private static Stream<Arguments> recursivelyEqualObjects() {
117118
person4.name = "John";
118119
person4.home.address.number = 1;
119120

120-
return Stream.of(Arguments.of(person1, person2, "same data, same type"),
121-
Arguments.of(person2, person1, "same data, same type reversed"),
122-
Arguments.of(person3, person4, "same data, different type"),
123-
Arguments.of(person4, person3, "same data, different type"));
121+
return Stream.of(arguments(person1, person2, "same data, same type"),
122+
arguments(person2, person1, "same data, same type reversed"),
123+
arguments(person3, person4, "same data, different type"),
124+
arguments(person4, person3, "same data, different type"));
124125
}
125126

126127
@SuppressWarnings("unused")
@@ -190,16 +191,16 @@ private static Stream<Arguments> recursivelyEqualObjectsIgnoringNullValues() {
190191
person8.neighbour.neighbour = new Person("James");
191192
person8.neighbour.neighbour.home.address.number = 456;
192193

193-
return Stream.of(Arguments.of(person1, person2, "same data, same type, except for actual null fields"),
194-
Arguments.of(person3, person1, "all actual fields are null, should be equal to anything"),
195-
Arguments.of(person3, person2, "all actual fields are null, should be equal to anything"),
196-
Arguments.of(person3, person3, "all actual fields are null, should be equal to anything"),
197-
Arguments.of(person3, person4, "same data, different type, actual has null fields"),
198-
Arguments.of(person5, person2, "same data, different type, actual has null fields"),
199-
Arguments.of(person6, person2, "same data, different type, actual has only non null name"),
200-
Arguments.of(person6, person7, "same data, actual has only non null name"),
201-
Arguments.of(person6, person8, "same data, actual has only non null name"),
202-
Arguments.of(person7, person8, "same data, actual has null fields deep in its graph"));
194+
return Stream.of(arguments(person1, person2, "same data, same type, except for actual null fields"),
195+
arguments(person3, person1, "all actual fields are null, should be equal to anything"),
196+
arguments(person3, person2, "all actual fields are null, should be equal to anything"),
197+
arguments(person3, person3, "all actual fields are null, should be equal to anything"),
198+
arguments(person3, person4, "same data, different type, actual has null fields"),
199+
arguments(person5, person2, "same data, different type, actual has null fields"),
200+
arguments(person6, person2, "same data, different type, actual has only non null name"),
201+
arguments(person6, person7, "same data, actual has only non null name"),
202+
arguments(person6, person8, "same data, actual has only non null name"),
203+
arguments(person7, person8, "same data, actual has null fields deep in its graph"));
203204
}
204205

205206
@SuppressWarnings("unused")
@@ -252,16 +253,16 @@ private static Stream<Arguments> recursivelyEqualObjectsIgnoringGivenFields() {
252253
person8.neighbour.neighbour = new Person("James");
253254
person8.neighbour.neighbour.home.address.number = 457;
254255

255-
return Stream.of(Arguments.of(person1, person2, "same data and type, except for one ignored field",
256+
return Stream.of(arguments(person1, person2, "same data and type, except for one ignored field",
256257
list("name")),
257-
Arguments.of(giant1, person1,
258+
arguments(giant1, person1,
258259
"different type, same data except name and height which is not even a field from person1",
259260
list("name", "height")),
260-
Arguments.of(person3, person4, "same data, different type, except for several ignored fields",
261+
arguments(person3, person4, "same data, different type, except for several ignored fields",
261262
list("name", "home.address.number")),
262-
Arguments.of(person5, person6, "same data except for one subfield of an ignored field",
263+
arguments(person5, person6, "same data except for one subfield of an ignored field",
263264
list("home")),
264-
Arguments.of(person7, person8, "same data except for one subfield of an ignored field",
265+
arguments(person7, person8, "same data except for one subfield of an ignored field",
265266
list("neighbour.neighbour.home.address.number", "neighbour.name")));
266267
}
267268

src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_ignoreOverriddenEquals_Test.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static org.assertj.core.api.Assertions.assertThat;
1616
import static org.assertj.core.api.recursive.comparison.FieldLocation.fielLocation;
1717
import static org.assertj.core.util.Lists.list;
18+
import static org.junit.jupiter.params.provider.Arguments.arguments;
1819

1920
import java.util.Date;
2021
import java.util.List;
@@ -34,6 +35,36 @@
3435
public class RecursiveComparisonAssert_isEqualTo_ignoreOverriddenEquals_Test
3536
extends RecursiveComparisonAssert_isEqualTo_BaseTest {
3637

38+
@SuppressWarnings("unused")
39+
@ParameterizedTest(name = "{2}: actual={0} / expected={1}")
40+
@MethodSource("comparison_ignores_all_fields_overridden_equals_methods_data")
41+
public void should_pass_when_comparison_ignores_all_fields_overridden_equals_methods(Object actual,
42+
Object expected,
43+
String testDescription) {
44+
assertThat(actual).usingRecursiveComparison()
45+
.ignoringAllOverriddenEquals()
46+
.isEqualTo(expected);
47+
}
48+
49+
@SuppressWarnings("unused")
50+
private static Stream<Arguments> comparison_ignores_all_fields_overridden_equals_methods_data() {
51+
Person person1 = new Person();
52+
person1.neighbour = new AlwaysDifferentPerson();
53+
Person person2 = new Person();
54+
person2.neighbour = new AlwaysDifferentPerson();
55+
56+
Person person3 = new Person();
57+
person3.home.address = new AlwaysDifferentAddress();
58+
person3.neighbour = new AlwaysDifferentPerson();
59+
Person person4 = new Person();
60+
person4.home.address = new AlwaysDifferentAddress();
61+
person4.neighbour = new AlwaysDifferentPerson();
62+
63+
return Stream.of(arguments(person1, person2, "AlwaysDifferentPerson neighbour identical field by field"),
64+
arguments(person3, person4,
65+
"AlwaysDifferentPerson neighbour and AlwaysDifferentAddress identical field by field"));
66+
}
67+
3768
// ignoringOverriddenEqualsForFieldsMatchingRegexes tests
3869

3970
@SuppressWarnings("unused")
@@ -62,12 +93,12 @@ private static Stream<Arguments> comparison_ignores_overridden_equals_methods_by
6293
person4.home.address = new AlwaysDifferentAddress();
6394
person4.neighbour = new AlwaysDifferentPerson();
6495

65-
return Stream.of(Arguments.of(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
96+
return Stream.of(arguments(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
6697
list("org.assertj.core.internal.objects.data.*")),
67-
Arguments.of(person3, person4,
98+
arguments(person3, person4,
6899
"AlwaysDifferentPerson neighbour and AlwaysDifferentAddress identical field by field",
69100
list(".*AlwaysDifferent.*")),
70-
Arguments.of(person3, person4, "Several regexes",
101+
arguments(person3, person4, "Several regexes",
71102
list(".*AlwaysDifferentPerson", ".*AlwaysDifferentAddress")));
72103
}
73104

@@ -140,9 +171,9 @@ private static Stream<Arguments> comparison_ignores_overridden_equals_methods_by
140171
person4.home.address = new AlwaysDifferentAddress();
141172
person4.neighbour = new AlwaysDifferentPerson();
142173

143-
return Stream.of(Arguments.of(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
174+
return Stream.of(arguments(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
144175
list(AlwaysDifferentPerson.class)),
145-
Arguments.of(person3, person4,
176+
arguments(person3, person4,
146177
"AlwaysDifferentPerson neighbour and AlwaysDifferentAddress identical field by field",
147178
list(AlwaysDifferentPerson.class, AlwaysDifferentAddress.class)));
148179
}
@@ -212,9 +243,9 @@ private static Stream<Arguments> comparison_ignores_overridden_equals_methods_by
212243
person4.home.address = new AlwaysDifferentAddress();
213244
person4.neighbour = new AlwaysDifferentPerson();
214245

215-
return Stream.of(Arguments.of(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
246+
return Stream.of(arguments(person1, person2, "AlwaysDifferentPerson neighbour identical field by field",
216247
list("neighbour")),
217-
Arguments.of(person3, person4,
248+
arguments(person3, person4,
218249
"AlwaysDifferentPerson neighbour and AlwaysDifferentAddress identical field by field",
219250
list("neighbour", "home.address")));
220251
}
@@ -248,7 +279,7 @@ public void should_fail_when_actual_differs_from_expected_as_some_overridden_equ
248279
}
249280

250281
@Test
251-
public void ignoring_overridden_equals_methods_only_applies_on_fields_but_not_on_the_object_under_test_itself() {
282+
public void overridden_equals_is_not_used_on_the_object_under_test_itself() {
252283
// GIVEN
253284
AlwaysEqualPerson actual = new AlwaysEqualPerson();
254285
actual.name = "John";

src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.assertj.core.test.AlwaysEqualComparator.alwaysEqual;
2222
import static org.assertj.core.test.Maps.mapOf;
2323
import static org.assertj.core.test.NeverEqualComparator.NEVER_EQUALS;
24+
import static org.junit.jupiter.params.provider.Arguments.arguments;
2425

2526
import java.sql.Timestamp;
2627
import java.util.Comparator;
@@ -81,11 +82,11 @@ private static Stream<Arguments> recursivelyEqualObjectsWhenUsingFieldComparator
8182
MapEntry<String, Comparator<?>> addressNumberComparator = entry("home.address.number", alwaysEqual());
8283
MapEntry<String, Comparator<?>> neighbourComparator = entry("neighbour", alwaysEqual());
8384

84-
return Stream.of(Arguments.of(person1, person2, mapOf(nameComparator, addressNumberComparator),
85+
return Stream.of(arguments(person1, person2, mapOf(nameComparator, addressNumberComparator),
8586
"same data except int fields and case for strings"),
86-
Arguments.of(person3, person4, mapOf(addressNumberComparator), "same data except for int fields"),
87+
arguments(person3, person4, mapOf(addressNumberComparator), "same data except for int fields"),
8788
// any neighbour differences should be ignored as we compare persons with AlwaysEqualComparator
88-
Arguments.of(person5, person6, mapOf(neighbourComparator),
89+
arguments(person5, person6, mapOf(neighbourComparator),
8990
"same data except for persons, person's fields should not be compared recursively except at the root level"));
9091
}
9192

src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withTypeComparators_Test.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TIMESTAMP;
1919
import static org.assertj.core.test.Maps.mapOf;
2020
import static org.assertj.core.test.NeverEqualComparator.NEVER_EQUALS;
21+
import static org.junit.jupiter.params.provider.Arguments.arguments;
2122

2223
import java.sql.Timestamp;
2324
import java.util.Comparator;
@@ -81,11 +82,11 @@ private static Stream<Arguments> recursivelyEqualObjectsWhenUsingTypeComparators
8182
MapEntry<Class<?>, Comparator<?>> stringComparator = entry(String.class, CaseInsensitiveStringComparator.instance);
8283
MapEntry<Class<?>, Comparator<?>> intComparator = entry(Integer.class, new AlwaysEqualComparator<Integer>());
8384
MapEntry<Class<?>, Comparator<?>> personComparator = entry(Person.class, new AlwaysEqualComparator<Person>());
84-
return Stream.of(Arguments.of(person1, person2, mapOf(stringComparator, intComparator),
85+
return Stream.of(arguments(person1, person2, mapOf(stringComparator, intComparator),
8586
"same data except int fields and case for strings"),
86-
Arguments.of(person3, person4, mapOf(intComparator), "same data except for int fields"),
87+
arguments(person3, person4, mapOf(intComparator), "same data except for int fields"),
8788
// any neighbour differences should be ignored as we compare persons with AlwaysEqualComparator
88-
Arguments.of(person5, person6, mapOf(personComparator),
89+
arguments(person5, person6, mapOf(personComparator),
8990
"same data except for persons, person's fields should not be compared recursively except at the root level"));
9091
}
9192

0 commit comments

Comments
 (0)