Skip to content

Commit

Permalink
Add ignoringCollectionOrder() to Recursive API
Browse files Browse the repository at this point in the history
  • Loading branch information
zeljko-mirovic committed May 5, 2019
1 parent 19da31d commit 9d3b9d0
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 11 deletions.
38 changes: 36 additions & 2 deletions src/main/java/org/assertj/core/api/RecursiveComparisonAssert.java
Expand Up @@ -533,7 +533,41 @@ public SELF ignoringOverriddenEqualsForFieldsMatchingRegexes(String... regexes)
}

/**
* Makes the recursive comparison to ignore collection order in the given the object under test fields. Nested fields can be specified like this: {@code home.address.street}.
* Makes the recursive comparison to ignore collection order in all fields in the object under test.
* <p>
* Example:
* <pre><code class='java'> public class Person {
* String name;
* List&lt;Person&gt; friends = new ArrayList&lt;&gt;();
* }
*
* Person sherlock1 = new Person("Sherlock Holmes");
* sherlock1.friends.add(new Person("Dr. John Watson"));
* sherlock1.friends.add(new Person("Molly Hooper"));
*
* Person sherlock2 = new Person("Sherlock Holmes");
* sherlock2.friends.add(new Person("Molly Hooper"));
* sherlock2.friends.add(new Person("Dr. John Watson"));
*
* // assertion succeeds as all fields collection order is ignored in the comparison
* assertThat(sherlock1).usingRecursiveComparison()
* .ignoringCollectionOrder()
* .isEqualTo(sherlock2);
*
* // assertion fails as fields collection order is not ignored in the comparison
* assertThat(sherlock1).usingRecursiveComparison()
* .isEqualTo(sherlock2);</code></pre>
*
* @return this {@link RecursiveComparisonAssert} to chain other methods.
*/
@CheckReturnValue
public SELF ignoringCollectionOrder() {
recursiveComparisonConfiguration.setIgnoreCollectionOrder(true);
return myself;
}

/**
* Makes the recursive comparison to ignore collection order in the object under test specified fields. Nested fields can be specified like this: {@code home.address.street}.
* <p>
* Example:
* <pre><code class='java'> public class Person {
Expand Down Expand Up @@ -574,7 +608,7 @@ public SELF ignoringCollectionOrderInFields(String... fieldsToIgnoreCollectionOr
}

/**
* Makes the recursive comparison to ignore collection order in the object under test fields matching the given regexes.
* Makes the recursive comparison to ignore collection order in the object under test fields matching the specified regexes.
* <p>
* Nested fields can be specified by using dots like this: {@code home\.address\.street} ({@code \} is used to escape
* dots since they have a special meaning in regexes).
Expand Down
Expand Up @@ -53,6 +53,7 @@ public class RecursiveComparisonConfiguration {
private boolean ignoreAllOverriddenEquals = false;

// ignore order in collections section
private boolean ignoreCollectionOrder = false;
private Set<FieldLocation> ignoredCollectionOrderInFields = new LinkedHashSet<>();
private List<Pattern> ignoredCollectionOrderInFieldsMatchingRegexes = new ArrayList<>();

Expand Down Expand Up @@ -187,6 +188,22 @@ public void ignoreOverriddenEqualsForTypes(Class<?>... types) {
ignoredOverriddenEqualsForTypes.addAll(list(types));
}

@VisibleForTesting
boolean getIgnoreCollectionOrder() {
return ignoreCollectionOrder;
}

/**
* Sets whether to ignore collection order in the comparison.
* <p>
* See {@link RecursiveComparisonAssert#ignoringCollectionOrder()} for code examples.
*
* @param ignoreCollectionOrder whether to ignore collection order in the comparison.
*/
public void setIgnoreCollectionOrder(boolean ignoreCollectionOrder) {
this.ignoreCollectionOrder = ignoreCollectionOrder;
}

/**
* Adds the given fields to the list of the object under test fields to ignore collection order in the recursive comparison.
* <p>
Expand Down Expand Up @@ -216,9 +233,9 @@ public Set<FieldLocation> getIgnoredCollectionOrderInFields() {
* @param regexes regexes used to find the object under test fields to ignore collection order in in the comparison.
*/
public void ignoreCollectionOrderInFieldsMatchingRegexes(String... regexes) {
ignoredCollectionOrderInFieldsMatchingRegexes.addAll(Stream.of(regexes)
.map(Pattern::compile)
.collect(toList()));
ignoredCollectionOrderInFieldsMatchingRegexes.addAll(Stream.of(regexes)
.map(Pattern::compile)
.collect(toList()));
}

/**
Expand Down Expand Up @@ -310,6 +327,7 @@ public String multiLineDescription(Representation representation) {
describeIgnoredFields(description);
describeIgnoredFieldsRegexes(description);
describeOverriddenEqualsMethodsUsage(description, representation);
describeIgnoreCollectionOrder(description);
describeIgnoredCollectionOrderInFields(description);
describeIgnoredCollectionOrderInFieldsMatchingRegexes(description);
describeRegisteredComparatorByTypes(description);
Expand Down Expand Up @@ -352,7 +370,8 @@ boolean shouldIgnoreOverriddenEqualsOf(Class<? extends Object> clazz) {
}

boolean shouldIgnoreCollectionOrder(DualValue dualKey) {
return matchesAnIgnoredCollectionOrderInField(dualKey)
return ignoreCollectionOrder
|| matchesAnIgnoredCollectionOrderInField(dualKey)
|| matchesAnIgnoredCollectionOrderInFieldRegex(dualKey);
}

Expand Down Expand Up @@ -411,9 +430,14 @@ private String describeIgnoredOverriddenEqualsForFields() {
return join(fieldsDescription).with(", ");
}

private void describeIgnoreCollectionOrder(StringBuilder description) {
if (ignoreCollectionOrder) description.append(format("- collection order were ignored in all fields in the comparison%n"));
}

private void describeIgnoredCollectionOrderInFields(StringBuilder description) {
if (!ignoredCollectionOrderInFields.isEmpty())
description.append(format("- collection order in the following fields were ignored in the comparison: %s%n", describeIgnoredCollectionOrderInFields()));
description.append(format("- collection order in the following fields were ignored in the comparison: %s%n",
describeIgnoredCollectionOrderInFields()));
}

private void describeIgnoredCollectionOrderInFieldsMatchingRegexes(StringBuilder description) {
Expand Down
Expand Up @@ -158,6 +158,16 @@ public void should_allow_to_ignore_overridden_equals_for_types() {
assertThat(configuration.getIgnoredOverriddenEqualsForTypes()).containsExactly(type1, type2);
}

@Test
public void should_allow_to_ignore_collection_order() {
// WHEN
RecursiveComparisonConfiguration configuration = assertThat(ACTUAL).usingRecursiveComparison()
.ignoringCollectionOrder()
.getRecursiveComparisonConfiguration();
// THEN
assertThat(configuration.getIgnoreCollectionOrder()).isTrue();
}

@Test
public void should_allow_to_ignore_collection_order_in_fields() {
// GIVEN
Expand Down
Expand Up @@ -26,7 +26,72 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class RecursiveComparisonAssert_isEqualTo_ignoringCollectionOrderInFields_Test extends RecursiveComparisonAssert_isEqualTo_BaseTest {
public class RecursiveComparisonAssert_isEqualTo_ignoringCollectionOrder_Test
extends RecursiveComparisonAssert_isEqualTo_BaseTest {

@ParameterizedTest(name = "{0}: actual={1} / expected={2}")
@MethodSource("should_pass_for_objects_with_the_same_data_when_collection_order_is_ignored_source")
@SuppressWarnings("unused")
public void should_pass_for_objects_with_the_same_data_when_collection_order_is_ignored(String description,
Object actual,
Object expected) {
assertThat(actual).usingRecursiveComparison()
.ignoringCollectionOrder()
.isEqualTo(expected);
}

@SuppressWarnings("unused")
private static Stream<Arguments> should_pass_for_objects_with_the_same_data_when_collection_order_is_ignored_source() {
FriendlyPerson friendlyPerson1 = new FriendlyPerson("Sherlock Holmes");
friendlyPerson1.friends.add(new FriendlyPerson("Dr. John Watson"));
friendlyPerson1.friends.add(new FriendlyPerson("Molly Hooper"));

FriendlyPerson friendlyPerson2 = new FriendlyPerson("Sherlock Holmes");
friendlyPerson2.friends.add(new FriendlyPerson("Molly Hooper"));
friendlyPerson2.friends.add(new FriendlyPerson("Dr. John Watson"));

FriendlyPerson friendlyPerson3 = new FriendlyPerson("Sherlock Holmes");
FriendlyPerson friendlyPerson4 = new FriendlyPerson("Dr. John Watson");
friendlyPerson4.friends.add(new FriendlyPerson("D.I. Greg Lestrade"));
friendlyPerson4.friends.add(new FriendlyPerson("Mrs. Hudson"));
friendlyPerson3.friends.add(friendlyPerson4);
friendlyPerson3.friends.add(new FriendlyPerson("Molly Hooper"));

FriendlyPerson friendlyPerson5 = new FriendlyPerson("Sherlock Holmes");
FriendlyPerson friendlyPerson6 = new FriendlyPerson("Dr. John Watson");
friendlyPerson6.friends.add(new FriendlyPerson("Mrs. Hudson"));
friendlyPerson6.friends.add(new FriendlyPerson("D.I. Greg Lestrade"));
friendlyPerson5.friends.add(friendlyPerson6);
friendlyPerson5.friends.add(new FriendlyPerson("Molly Hooper"));

return Stream.of(arguments("same data except friends property collection order",
friendlyPerson1, friendlyPerson2),
arguments("same data except friends property order in subfield collection",
friendlyPerson3, friendlyPerson5));
}

@Test
public void should_fail_when_actual_differs_from_expected_even_when_collection_order_is_ignored() {
// GIVEN
FriendlyPerson actual = new FriendlyPerson("Sherlock Holmes");
actual.home.address.number = 1;
actual.friends.add(new FriendlyPerson("Dr. John Watson"));
actual.friends.add(new FriendlyPerson("Molly Hooper"));

FriendlyPerson expected = new FriendlyPerson("Sherlock Holmes");
expected.home.address.number = 2;
expected.friends.add(new FriendlyPerson("Molly Hooper"));
expected.friends.add(new FriendlyPerson("Dr. John Watson"));

recursiveComparisonConfiguration.setIgnoreCollectionOrder(true);

// WHEN
compareRecursivelyFailsAsExpected(actual, expected);

// THEN
ComparisonDifference comparisonDifference = new ComparisonDifference(list("home.address.number"), 1, 2);
verifyShouldBeEqualByComparingFieldByFieldRecursivelyCall(actual, expected, comparisonDifference);
}

@ParameterizedTest(name = "{0}: actual={1} / expected={2} / ignore collection order in fields={3}")
@MethodSource("should_pass_for_objects_with_the_same_data_when_collection_order_is_ignored_in_specified_fields_source")
Expand Down
Expand Up @@ -136,6 +136,16 @@ public void should_show_the_ignored_overridden_equals_methods_fields() {
// @format:on
}

@Test
public void should_show_the_ignored_collection_order() {
// GIVEN
recursiveComparisonConfiguration.setIgnoreCollectionOrder(true);
// WHEN
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
assertThat(multiLineDescription).contains(format("- collection order were ignored in all fields in the comparison%n"));
}

@Test
public void should_show_the_ignored_collection_order_in_fields() {
// GIVEN
Expand Down Expand Up @@ -220,6 +230,7 @@ public void should_show_a_complete_multiline_description() {
recursiveComparisonConfiguration.ignoreOverriddenEqualsForFieldsMatchingRegexes(".*oo", ".ar", "oo.ba");
recursiveComparisonConfiguration.ignoreOverriddenEqualsForTypes(String.class, Multimap.class);
recursiveComparisonConfiguration.ignoreOverriddenEqualsForFields("foo", "baz", "foo.baz");
recursiveComparisonConfiguration.setIgnoreCollectionOrder(true);
recursiveComparisonConfiguration.ignoreCollectionOrderInFields("foo", "bar", "foo.bar");
recursiveComparisonConfiguration.ignoreCollectionOrderInFieldsMatchingRegexes("f.*", "ba.", "foo.*");
recursiveComparisonConfiguration.registerComparatorForType(new AbsValueComparator<>(), Integer.class);
Expand All @@ -238,6 +249,7 @@ public void should_show_a_complete_multiline_description() {
" - the following fields: foo, baz, foo.baz%n" +
" - the following types: java.lang.String, com.google.common.collect.Multimap%n" +
" - the types matching the following regexes: .*oo, .ar, oo.ba%n" +
"- collection order were ignored in all fields in the comparison%n" +
"- collection order in the following fields were ignored in the comparison: foo, bar, foo.bar%n" +
"- collection order in the fields matching the following regexes were ignored in the comparison: f.*, ba., foo.*%n" +
"- these types were compared with the following comparators:%n" +
Expand Down
Expand Up @@ -36,6 +36,23 @@ public void setup() {
recursiveComparisonConfiguration = new RecursiveComparisonConfiguration();
}

@ParameterizedTest(name = "{0} collection order should be ignored")
@MethodSource("should_ignore_collection_order_source")
public void should_ignore_collection_order(DualValue dualKey) {
// GIVEN
recursiveComparisonConfiguration.setIgnoreCollectionOrder(true);
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnoreCollectionOrder(dualKey);
// THEN
assertThat(ignored).as("%s collection order should be ignored", dualKey).isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> should_ignore_collection_order_source() {
return Stream.of(arguments(dualKeyWithPath("name")),
arguments(dualKeyWithPath("name", "first")));
}

@Test
public void should_register_ignore_collection_order_in_fields_without_duplicates() {
// GIVEN
Expand Down Expand Up @@ -100,8 +117,8 @@ private static Stream<Arguments> should_ignore_collection_order_in_fields_matchi
}

@ParameterizedTest(name = "{0} collection order should be ignored")
@MethodSource("should_ignore_collection_order_source")
public void should_ignore_collection_order(DualValue dualKey) {
@MethodSource("should_ignore_collection_order_in_fields_source")
public void should_ignore_collection_order_in_fields(DualValue dualKey) {
// GIVEN
recursiveComparisonConfiguration.ignoreCollectionOrderInFieldsMatchingRegexes(".*name");
recursiveComparisonConfiguration.ignoreCollectionOrderInFields("number");
Expand All @@ -112,7 +129,7 @@ public void should_ignore_collection_order(DualValue dualKey) {
}

@SuppressWarnings("unused")
private static Stream<Arguments> should_ignore_collection_order_source() {
private static Stream<Arguments> should_ignore_collection_order_in_fields_source() {
return Stream.of(arguments(dualKeyWithPath("name")),
arguments(dualKeyWithPath("number")),
arguments(dualKeyWithPath("surname")),
Expand Down

0 comments on commit 9d3b9d0

Please sign in to comment.