diff --git a/src/main/java/org/assertj/core/api/AbstractObjectAssert.java b/src/main/java/org/assertj/core/api/AbstractObjectAssert.java index 44a58fb841..d63d9b4e95 100644 --- a/src/main/java/org/assertj/core/api/AbstractObjectAssert.java +++ b/src/main/java/org/assertj/core/api/AbstractObjectAssert.java @@ -26,6 +26,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.assertj.core.annotations.Beta; import org.assertj.core.api.recursive.comparison.RecursiveComparisonAssert; import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; import org.assertj.core.description.Description; @@ -772,11 +773,70 @@ public SELF returns(T expected, Function from) { return myself; } + /** + * Enable using a recursive field by field comparison strategy when calling the chained {@link RecursiveComparisonAssert#isEqualTo(Object) isEqualTo} assertion. + *

+ * Example: + *

 public class Person {
+   *   String name;
+   *   double height;
+   *   Home home = new Home();
+   * }
+   *
+   * public class Home {
+   *   Address address = new Address();
+   *   Date ownedSince;
+   * }
+   *
+   * public static class Address {
+   *   int number;
+   *   String street;
+   * }
+   *
+   * Person sherlock = new Person("Sherlock", 1.80);
+   * sherlock.home.ownedSince = new Date(123);
+   * sherlock.home.address.street = "Baker Street";
+   * sherlock.home.address.number = 221;
+   *
+   * Person sherlock2 = new Person("Sherlock", 1.80);
+   * sherlock2.home.ownedSince = new Date(123);
+   * sherlock2.home.address.street = "Baker Street";
+   * sherlock2.home.address.number = 221;
+   *
+   * // assertion succeeds as the data of both objects are the same.
+   * assertThat(sherlock).usingRecursiveComparison()
+   *                     .isEqualTo(sherlock2);
+   *
+   * // assertion fails because sherlock.equals(sherlock2) is false.
+   * assertThat(sherlock).isEqualTo(sherlock2);
+ *

+ * The recursive comparison is performed according to the default {@link RecursiveComparisonConfiguration} that is: + *

+ * + * @return a new {@link RecursiveComparisonAssert} instance + */ + @Beta public RecursiveComparisonAssert usingRecursiveComparison() { return usingRecursiveComparison(new RecursiveComparisonConfiguration()); } // TODO soft assertion tests: add to method changing the object under tests + + /** + * Same as {@link #usingRecursiveComparison()} but allows to specify your own {@link RecursiveComparisonConfiguration}. + * @param recursiveComparisonConfiguration the {@link RecursiveComparisonConfiguration} used in the chained {@link RecursiveComparisonAssert#isEqualTo(Object) isEqualTo} assertion. + * + * @return a new {@link RecursiveComparisonAssert} instance built with the given {@link RecursiveComparisonConfiguration}. + */ + @Beta public RecursiveComparisonAssert usingRecursiveComparison(RecursiveComparisonConfiguration recursiveComparisonConfiguration) { return new RecursiveComparisonAssert(actual, recursiveComparisonConfiguration); } diff --git a/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java b/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java index d7ac62d3a9..10e8f136bc 100644 --- a/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java +++ b/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert.java @@ -12,13 +12,11 @@ */ package org.assertj.core.api.recursive.comparison; -import static org.assertj.core.api.recursive.comparison.FieldLocation.fielLocation; import static org.assertj.core.error.ShouldBeEqualByComparingFieldByFieldRecursively.shouldBeEqualByComparingFieldByFieldRecursively; import java.util.Comparator; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.stream.Stream; import org.assertj.core.api.WritableAssertionInfo; @@ -62,16 +60,25 @@ public RecursiveComparisonAssert as(String description, Object... args) { /** * Asserts that the object under test (actual) is equal to the given object when compared field by field recursively (including - * inherited fields are included in the comparison).If the comparison fails it will report all the differences found and which + * inherited fields are included in the comparison). If the comparison fails it will report all the differences found and which * effective {@link RecursiveComparisonConfiguration} was used to help users understand the failure. * TODO add link to assertj website documentation *

* This is typically useful when actual's {@code equals} was not overridden. *

- * The comparison is not symmetrical since it is limited to actual's fields, the algorithm firts gather all actual's fields + * The comparison is not symmetrical since it is limited to actual's fields, the algorithm gather all actual's fields * and then compare them to the corresponding expected's fields. * It is then possible for the expected object to have more fields than actual which is handy when comparing a base type to a subtype. *

+ * Strict/lenient recursive comparison + *

+ * By default the objects to compare can be of different types but must have the same properties/fields. For example if object under test has a {@code work} field of type {@code Address}, + * the expected object to compare the object under test to must also have one but it can of a different type like {@code AddressDto}. + *

+ * It is possible to enforce strict type checking by calling {@link #withStrictTypeChecking()} and make the comparison fail whenever the compared objects or their fields are not compatible.
+ * Compatible means that the expected object/field types are the same or a subtype of actual/field types, for example if actual is an {@code Animal} and expected a {@code Dog}, they will be compared fiels by field in strict type checking mode. + *

+ *

* Ignoring null fields in the recursive comparison *

* When an object is partially populated, it can still be interesting to see if its populated values are correct against a fully populated object. @@ -81,8 +88,8 @@ public RecursiveComparisonAssert as(String description, Object... args) { *

* Recursive comparison use of overridden {@code equals} methods *

- * By default the recursive comparison is not applied on fields whose classes have an overridden {@code equals} implementation, - * concretely it means {@code equals} is used to compare these fields instead of keeing on applying the recursive comparison. + * By default the recursive comparison is not applied on fields whose classes have overridden the {@code equals} method, + * concretely it means {@code equals} is used to compare these fields instead of keeping on applying the recursive comparison. * The rationale is that if a class has redefined {@code equals} then it should be used to compare instances unless having a good reason. *

* It is possible though to change this behavior and force recursive comparison by calling any of these methods (but before calling {@code isEqualTo} otherwise this has no effect!): @@ -90,64 +97,57 @@ public RecursiveComparisonAssert as(String description, Object... args) { *

  • {@link #ignoringOverriddenEqualsForTypes(Class...)} Any fields of these classes are compared recursively
  • *
  • {@link #ignoringOverriddenEqualsForFields(String...)} Any given fields are compared recursively
  • *
  • {@link #ignoringOverriddenEqualsForFieldsMatchingRegexes(String...)} Any fields matching one of these regexes are compared recursively
  • + *
  • {@link #ignoringAllOverriddenEquals()} except for basic types (TODO define basic types), all fields are compared field by field recursively.
  • * + * Recursive comparison and cycles *

    - * The recursive comparison handles cycles. By default {@code floats} are compared with a precision of 1.0E-6 and {@code doubles} with 1.0E-15. + * The recursive comparison handles cycles. *

    - * You can specify a custom comparator per (nested) fields or type with respectively {@code #usingComparatorForFields(Comparator, String...) usingComparatorForFields(Comparator, String...)} - * and {@code #usingComparatorForType(Comparator, Class)}. + * Comparator used in the recursive comparison *

    - * The objects to compare can be of different types but must have the same properties/fields. For example if actual object has a name String field, it is expected the other object to also have one. - * If an object has a field and a property with the same name, the property value will be used over the field. + * By default {@code floats} are compared with a precision of 1.0E-6 and {@code doubles} with 1.0E-15. *

    - * Example: + * You can specify a custom comparator per (nested) fields or type with the methods below (but before calling {@code isEqualTo} otherwise this has no effect!): + *

      + *
    1. {@link #withComparatorForFields(Comparator, String...) withComparatorForFields(Comparator, String...)} for one or multiple fields
    2. + *
    3. {@link #withComparatorForType(Comparator, Class)} for a given type
    4. + *
    + *

    + * Note that field comparators always take precedence over type comparators. + *

    + * Example + *

    + * Here is a basic example with a default {@link RecursiveComparisonConfiguration}, you can find other examples for each of the method changing the recursive comparison behavior + * like {@link #ignoringFields(String...)}. *

     public class Person {
    -   *   public String name;
    -   *   public double height;
    -   *   public Home home = new Home();
    -   *   public Person bestFriend;
    -   *   // constructor with name and height omitted for brevity
    +   *   String name;
    +   *   double height;
    +   *   Home home = new Home();
        * }
        *
        * public class Home {
    -   *   public Address address = new Address();
    +   *   Address address = new Address();
    +   *   Date ownedSince;
        * }
        *
        * public static class Address {
    -   *   public int number = 1;
    +   *   int number;
    +   *   String street;
        * }
        *
    -   * Person jack = new Person("Jack", 1.80);
    -   * jack.home.address.number = 123;
    -   *
    -   * Person jackClone = new Person("Jack", 1.80);
    -   * jackClone.home.address.number = 123;
    -   *
    -   * // cycle are handled in comparison
    -   * jack.bestFriend = jackClone;
    -   * jackClone.bestFriend = jack;
    -   *
    -   * // will fail as equals compares object references
    -   * assertThat(jack).isEqualTo(jackClone);
    -   *
    -   * // jack and jackClone are equals when doing a recursive field by field comparison
    -   * assertThat(jack).isEqualToComparingFieldByFieldRecursively(jackClone);
    -   *
    -   * // any type/field can be compared with a a specific comparator.
    -   * // let's change  jack's height a little bit
    -   * jack.height = 1.81;
    -   *
    -   * // assertion fails because of the height difference
    -   * // (the default precision comparison for double is 1.0E-15)
    -   * assertThat(jack).isEqualToComparingFieldByFieldRecursively(jackClone);
    +   * Person sherlock = new Person("Sherlock", 1.80);
    +   * sherlock.home.ownedSince = new Date(123);
    +   * sherlock.home.address.street = "Baker Street";
    +   * sherlock.home.address.number = 221;
        *
    -   * // this succeeds because we allow a 0.5 tolerance on double
    -   * assertThat(jack).usingComparatorForType(new DoubleComparator(0.5), Double.class)
    -   *                 .isEqualToComparingFieldByFieldRecursively(jackClone);
    +   * Person sherlock2 = new Person("Sherlock", 1.80);
    +   * sherlock2.home.ownedSince = new Date(123);
    +   * sherlock2.home.address.street = "Baker Street";
    +   * sherlock2.home.address.number = 221;
        *
    -   * // you can set a comparator on specific fields (nested fields are supported)
    -   * assertThat(jack).usingComparatorForFields(new DoubleComparator(0.5), "height")
    -   *                 .isEqualToComparingFieldByFieldRecursively(jackClone);
    + * // assertion succeeds as the data of both objects are the same. + * assertThat(sherlock).usingRecursiveComparison() + * .isEqualTo(sherlock2); * * @param expected the object to compare {@code actual} to. * @return {@code this} assertion object. @@ -183,8 +183,6 @@ public RecursiveComparisonAssert isEqualTo(Object expected) { * String name; * double height; * Home home = new Home(); - * Person bestFriend; - * // constructor omitted for brevity * } * * public class Home { @@ -323,7 +321,7 @@ public RecursiveComparisonAssert ignoringFieldsMatchingRegexes(String... regexes * - at some point we need to compare something! *

    * For the recursive comparison to use the overridden {@code equals} of a given type anyway (like {@link Date}) you can register - * a type comparator using {@link #withComparatorForType(Class, Comparator)}. + * a type comparator using {@link #withComparatorForType(Comparator, Class)}. *

    * TODO introduce {@code ignoringAllOverriddenEqualsExceptFor(Class... classes)} ? *

    @@ -612,32 +610,86 @@ public RecursiveComparisonAssert withStrictTypeChecking() { return this; } + /** + * Allows to register a specific comparator to compare fields with the given locations. + * A typical usage is for comparing double/float fields with a given precision. + *

    + * Comparators specified by this method have precedence over comparators added with {@link #withComparatorForType(Comparator, Class)}. + *

    + * The field locations must be specified from the root object, + * for example if {@code Foo} has a {@code Bar} field which has an {@code id}, one can register to a comparator for Bar's {@code id} by calling: + *

     withComparatorForField("bar.id", idComparator)
    + *

    + * Complete example: + *

     public class TolkienCharacter {
    +   *   String name;
    +   *   double height;
    +   * }
    +   *
    +   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
    +   * TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
    +   * TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
    +   *
    +   * Comparator<Double> closeEnough = (d1, d2) -> Math.abs(d1 - d2) <= 0.5 ? 0 : 1;
    +   *
    +   * // assertions succeed
    +   * assertThat(frodo).usingRecursiveComparison()
    +   *                  .withComparatorForFields(closeEnough, "height")
    +   *                  .isEqualTo(tallerFrodo);
    +   *
    +   * // assertion fails
    +   * assertThat(frodo).usingRecursiveComparison()
    +   *                  .withComparatorForFields(closeEnough, "height")
    +   *                  .isEqualTo(reallyTallFrodo);
    + * + * @param comparator the {@link java.util.Comparator Comparator} to use to compare the given fields + * @param fieldLocations the location from the root object of the fields the comparator should be used for + * @return this {@link RecursiveComparisonAssert} to chain other methods. + */ @CheckReturnValue - public RecursiveComparisonAssert withComparatorForField(String fieldLocation, Comparator comparator) { - recursiveComparisonConfiguration.registerComparatorForField(fielLocation(fieldLocation), comparator); - return this; - } - - @SafeVarargs - @CheckReturnValue - public final RecursiveComparisonAssert withComparatorForFields(Map.Entry>... comparatorByFields) { - Stream.of(comparatorByFields).forEach(this::withComparatorForField); - return this; - } - - @CheckReturnValue - public RecursiveComparisonAssert withComparatorForType(Class type, Comparator comparator) { - recursiveComparisonConfiguration.registerComparatorForType(type, comparator); + public RecursiveComparisonAssert withComparatorForFields(Comparator comparator, String... fieldLocations) { + Stream.of(fieldLocations) + .map(FieldLocation::new) + .forEach(fieldLocation -> recursiveComparisonConfiguration.registerComparatorForField(comparator, fieldLocation)); return this; } - // can't type Comparator/Class with since each entry is about different types (no reason to be all related to T) - @SuppressWarnings({ "rawtypes", "unchecked" }) - @SafeVarargs + /** + * Allows to register a specific comparator to compare the fields with the given type. + * A typical usage is for comparing double/float fields with a given precision. + *

    + * Comparators specified by this method have less precedence than comparators added with {@link #withComparatorForFields(Comparator, String...) withComparatorForFields(Comparator, String...)}. + *

    + * Example: + *

     public class TolkienCharacter {
    +   *   String name;
    +   *   double height;
    +   * }
    +   *
    +   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
    +   * TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
    +   * TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
    +   *
    +   * Comparator<Double> closeEnough = (d1, d2) -> Math.abs(d1 - d2) <= 0.5 ? 0 : 1;
    +   *
    +   * // assertions succeed
    +   * assertThat(frodo).usingRecursiveComparison()
    +   *                  .withComparatorForType(closeEnough, Double.class)
    +   *                  .isEqualTo(tallerFrodo);
    +   *
    +   * // assertion fails
    +   * assertThat(frodo).usingRecursiveComparison()
    +   *                  .withComparatorForType(closeEnough, Double.class)
    +   *                  .isEqualTo(reallyTallFrodo);
    + * + * @param comparator the {@link java.util.Comparator Comparator} to use to compare the given fields + * @param type the type to be compared with the given comparator. + * + * @return this {@link RecursiveComparisonAssert} to chain other methods. + */ @CheckReturnValue - public final RecursiveComparisonAssert withComparatorForTypes(Map.Entry... comparatorByTypes) { - Stream.of(comparatorByTypes) - .forEach(comparatorByType -> withComparatorForType(comparatorByType.getKey(), comparatorByType.getValue())); + public RecursiveComparisonAssert withComparatorForType(Comparator comparator, Class type) { + recursiveComparisonConfiguration.registerComparatorForType(comparator, type); return this; } @@ -650,9 +702,4 @@ private List determineDifferencesWith(Object expected) { return recursiveComparisonDifferenceCalculator.determineDifferences(actual, expected, recursiveComparisonConfiguration); } - // syntactic sugar - private RecursiveComparisonAssert withComparatorForField(Map.Entry> comparatorByField) { - return withComparatorForField(comparatorByField.getKey(), comparatorByField.getValue()); - } - } diff --git a/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration.java b/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration.java index 0f93316821..13bd3c96ad 100644 --- a/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration.java +++ b/src/main/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration.java @@ -171,11 +171,35 @@ public void ignoreOverriddenEqualsForTypes(Class... types) { ignoredOverriddenEqualsForTypes.addAll(list(types)); } - public void registerComparatorForType(Class type, Comparator comparator) { + /** + * Registers the given {@link Comparator} to compare the fields with the given type. + *

    + * Comparators specified by this method have less precedence than comparators added with {@link #registerComparatorForField(Comparator, FieldLocation)}. + *

    + * See {@link RecursiveComparisonAssert#withComparatorForType(Comparator, Class)} for examples. + * + * @param comparator the {@link java.util.Comparator Comparator} to use to compare the given field + * @param type the type to be compared with the given comparator. + */ + public void registerComparatorForType(Comparator comparator, Class type) { typeComparators.put(type, comparator); } - public void registerComparatorForField(FieldLocation fieldLocation, Comparator comparator) { + /** + * Registers the given {@link Comparator} to compare the fields with the given locations. + *

    + * The field locations must be specified from the root object, + * for example if {@code Foo} has a {@code Bar} field which has an {@code id}, one can register to a comparator for Bar's {@code id} by calling: + *

     registerComparatorForField(new FieldLocation("bar.id"), idComparator)
    + *

    + * Comparators specified by this method have precedence over comparators added with {@link #registerComparatorForType(Comparator, Class)}. + *

    + * See {@link RecursiveComparisonAssert#withComparatorForFields(Comparator, String...) RecursiveComparisonAssert#withComparatorForFields(Comparator, String...)} for examples. + * + * @param comparator the {@link java.util.Comparator Comparator} to use to compare the given field + * @param fieldLocation the location from the root object of the field the comparator should be used for + */ + public void registerComparatorForField(Comparator comparator, FieldLocation fieldLocation) { fieldComparators.registerComparator(fieldLocation, comparator); } diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_fluent_API_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_fluent_API_Test.java index cc9c4dd739..c83d831951 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_fluent_API_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_fluent_API_Test.java @@ -169,9 +169,8 @@ public void should_allow_to_register_field_comparators() { // WHEN // @format:off RecursiveComparisonConfiguration configuration = assertThat(ACTUAL).usingRecursiveComparison() - .withComparatorForField(field1, alwaysEqualComparator) - .withComparatorForFields(entry(field2, alwaysDifferentComparator), - entry(field3, alwaysEqualComparator)) + .withComparatorForFields(alwaysEqualComparator, field1, field3) + .withComparatorForFields(alwaysDifferentComparator, field2) .getRecursiveComparisonConfiguration(); // @format:on // THEN @@ -185,13 +184,13 @@ public void should_allow_to_register_type_comparators() { // GIVEN Class type1 = String.class; Class type2 = Timestamp.class; - Class type3 = Tuple.class; + Class type3 = Tuple.class; // WHEN // @format:off RecursiveComparisonConfiguration configuration = assertThat(ACTUAL).usingRecursiveComparison() - .withComparatorForType(type1, ALWAY_EQUALS_STRING) - .withComparatorForTypes(entry(type2, ALWAY_EQUALS_TIMESTAMP), - entry(type3, ALWAY_EQUALS_TUPLE)) + .withComparatorForType(ALWAY_EQUALS_STRING, type1) + .withComparatorForType(ALWAY_EQUALS_TIMESTAMP, type2) + .withComparatorForType(ALWAY_EQUALS_TUPLE, type3) .getRecursiveComparisonConfiguration(); // @format:on // THEN @@ -208,8 +207,8 @@ public void should_allow_to_override_field_comparator() { AlwaysDifferentComparator alwaysDifferentComparator = alwaysDifferent(); // WHEN RecursiveComparisonConfiguration configuration = assertThat(ACTUAL).usingRecursiveComparison() - .withComparatorForField(field, alwaysEqualComparator) - .withComparatorForField(field, alwaysDifferentComparator) + .withComparatorForFields(alwaysEqualComparator, field) + .withComparatorForFields(alwaysDifferentComparator, field) .getRecursiveComparisonConfiguration(); // THEN assertThat(configuration.getComparatorForField(field)).isSameAs(alwaysDifferentComparator); @@ -223,8 +222,8 @@ public void should_allow_to_override_type_comparator() { AlwaysDifferentComparator alwaysDifferentComparator = alwaysDifferent(); // WHEN RecursiveComparisonConfiguration configuration = assertThat(ACTUAL).usingRecursiveComparison() - .withComparatorForType(type, alwaysEqualComparator) - .withComparatorForType(type, alwaysDifferentComparator) + .withComparatorForType(alwaysEqualComparator, type) + .withComparatorForType(alwaysDifferentComparator, type) .getRecursiveComparisonConfiguration(); // THEN assertThat(configuration.getComparatorForType(type)).isSameAs(alwaysDifferentComparator); diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_strictTypeCheck_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_strictTypeCheck_Test.java index f973a3936d..1898dbe6f6 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_strictTypeCheck_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_strictTypeCheck_Test.java @@ -109,7 +109,6 @@ public void should_fail_in_strict_type_checking_mode_when_actual_and_expected_ha compareRecursivelyFailsAsExpected(actual, expected); // THEN - // as neighbour comparison fails, the comparison skip any neighbour fields ComparisonDifference difference = diff("", actual, expected, "actual and expected are considered different since the comparison enforces strict type check and expected type org.assertj.core.internal.objects.data.PersonDtoWithPersonNeighbour is not a subtype of actual type org.assertj.core.internal.objects.data.Person"); verifyShouldBeEqualByComparingFieldByFieldRecursivelyCall(actual, expected, difference); diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test.java index e691d3bfa0..0e78ab694e 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test.java @@ -13,23 +13,20 @@ package org.assertj.core.api.recursive.comparison; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.recursive.comparison.FieldLocation.fielLocation; import static org.assertj.core.internal.objects.SymmetricDateComparator.SYMMETRIC_DATE_COMPARATOR; import static org.assertj.core.test.AlwaysDifferentComparator.alwaysDifferent; import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TIMESTAMP; import static org.assertj.core.test.AlwaysEqualComparator.alwaysEqual; -import static org.assertj.core.test.Maps.mapOf; import static org.assertj.core.test.NeverEqualComparator.NEVER_EQUALS; +import static org.assertj.core.util.Arrays.array; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.sql.Timestamp; import java.util.Comparator; import java.util.Date; -import java.util.Map; import java.util.stream.Stream; -import org.assertj.core.data.MapEntry; import org.assertj.core.internal.AtPrecisionComparator; import org.assertj.core.internal.CaseInsensitiveStringComparator; import org.assertj.core.internal.objects.data.Giant; @@ -44,14 +41,15 @@ public class RecursiveComparisonAssert_isEqualTo_withFieldComparators_Test extends RecursiveComparisonAssert_isEqualTo_BaseTest { @SuppressWarnings("unused") - @ParameterizedTest(name = "{3}: actual={0} / expected={1} - comparatorsByType: {2}") + @ParameterizedTest(name = "{4}: actual={0} / expected={1} - comparators {2} - fields {3}") @MethodSource("recursivelyEqualObjectsWhenUsingFieldComparators") public void should_pass_for_objects_with_the_same_data_when_using_registered_comparators_by_fields(Object actual, Object expected, - Map> comparatorByFields, + Comparator comparator, + String[] fields, String testDescription) { assertThat(actual).usingRecursiveComparison() - .withComparatorForFields(comparatorByFields.entrySet().toArray(new Map.Entry[0])) + .withComparatorForFields(comparator, fields) .isEqualTo(expected); } @@ -60,11 +58,11 @@ private static Stream recursivelyEqualObjectsWhenUsingFieldComparator Person person1 = new Person("John"); person1.home.address.number = 1; Person person2 = new Person("JoHN"); - person2.home.address.number = 2; + person2.home.address.number = 1; Person person3 = new Person("John"); person3.home.address.number = 1; - Person person4 = new Person("John"); + Person person4 = new Person("Jack"); person4.home.address.number = 2; Person person5 = new Person("John"); @@ -78,16 +76,13 @@ private static Stream recursivelyEqualObjectsWhenUsingFieldComparator person6.neighbour = new Person("Jim"); person6.neighbour.home.address.number = 456; - MapEntry> nameComparator = entry("name", CaseInsensitiveStringComparator.instance); - MapEntry> addressNumberComparator = entry("home.address.number", alwaysEqual()); - MapEntry> neighbourComparator = entry("neighbour", alwaysEqual()); - - return Stream.of(arguments(person1, person2, mapOf(nameComparator, addressNumberComparator), - "same data except int fields and case for strings"), - arguments(person3, person4, mapOf(addressNumberComparator), "same data except for int fields"), + return Stream.of(arguments(person1, person2, CaseInsensitiveStringComparator.instance, array("name"), + "same data except int fields and case for strings"), + arguments(person3, person4, alwaysEqual(), array("name", "home.address.number"), + "same data except for int fields"), // any neighbour differences should be ignored as we compare persons with AlwaysEqualComparator - arguments(person5, person6, mapOf(neighbourComparator), - "same data except for persons, person's fields should not be compared recursively except at the root level")); + arguments(person5, person6, alwaysEqual(), array("neighbour"), + "same data except for persons, person's fields should not be compared recursively except at the root level")); } @Test @@ -105,8 +100,8 @@ public void should_fail_when_actual_differs_from_expected_when_using_field_compa expected.neighbour = new Person("Jack"); expected.neighbour.home.address.number = 123; // register comparators for some fields that will fail the comparison - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("dateOfBirth"), alwaysDifferent()); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("neighbour.home.address"), alwaysDifferent()); + recursiveComparisonConfiguration.registerComparatorForField(alwaysDifferent(), fielLocation("dateOfBirth")); + recursiveComparisonConfiguration.registerComparatorForField(alwaysDifferent(), fielLocation("neighbour.home.address")); // WHEN compareRecursivelyFailsAsExpected(actual, expected); @@ -133,7 +128,7 @@ public void should_be_able_to_compare_objects_recursively_using_some_precision_f // THEN assertThat(goliath).usingRecursiveComparison() - .withComparatorForField("height", new AtPrecisionComparator<>(0.2)) + .withComparatorForFields(new AtPrecisionComparator<>(0.2), "height") .isEqualTo(goliathTwin); } @@ -144,7 +139,7 @@ public void should_handle_null_field_with_field_comparator() { Patient expected = new Patient(new Timestamp(3L)); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForField("dateOfBirth", ALWAY_EQUALS_TIMESTAMP) + .withComparatorForFields(ALWAY_EQUALS_TIMESTAMP, "dateOfBirth") .isEqualTo(expected); } @@ -156,7 +151,7 @@ public void should_ignore_comparators_when_fields_are_the_same() { Patient expected = new Patient(dateOfBirth); // WHEN assertThat(actual).usingRecursiveComparison() - .withComparatorForField("dateOfBirth", NEVER_EQUALS) + .withComparatorForFields(NEVER_EQUALS, "dateOfBirth") .isEqualTo(expected); } @@ -169,10 +164,10 @@ public void should_treat_timestamp_as_equal_to_date_when_registering_a_date_symm other.dateOfBirth = new Date(1000L); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForField("dateOfBirth", SYMMETRIC_DATE_COMPARATOR) + .withComparatorForFields(SYMMETRIC_DATE_COMPARATOR, "dateOfBirth") .isEqualTo(other); assertThat(other).usingRecursiveComparison() - .withComparatorForField("dateOfBirth", SYMMETRIC_DATE_COMPARATOR) + .withComparatorForFields(SYMMETRIC_DATE_COMPARATOR, "dateOfBirth") .isEqualTo(actual); } @@ -183,12 +178,12 @@ public void field_comparator_should_take_precedence_over_type_comparator_whateve Patient expected = new Patient(new Timestamp(3L)); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForType(Timestamp.class, NEVER_EQUALS) - .withComparatorForField("dateOfBirth", ALWAY_EQUALS_TIMESTAMP) + .withComparatorForType(NEVER_EQUALS, Timestamp.class) + .withComparatorForFields(ALWAY_EQUALS_TIMESTAMP, "dateOfBirth") .isEqualTo(expected); assertThat(actual).usingRecursiveComparison() - .withComparatorForField("dateOfBirth", ALWAY_EQUALS_TIMESTAMP) - .withComparatorForType(Timestamp.class, NEVER_EQUALS) + .withComparatorForFields(ALWAY_EQUALS_TIMESTAMP, "dateOfBirth") + .withComparatorForType(NEVER_EQUALS, Timestamp.class) .isEqualTo(expected); } diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withTypeComparators_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withTypeComparators_Test.java index 52eb749837..16cf6dc9a5 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withTypeComparators_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonAssert_isEqualTo_withTypeComparators_Test.java @@ -50,10 +50,13 @@ public void should_pass_for_objects_with_the_same_data_when_using_registered_com Object expected, Map, Comparator> comparatorByTypes, String testDescription) { - assertThat(actual).usingRecursiveComparison() - .withComparatorForTypes(comparatorByTypes.entrySet().toArray(new Map.Entry[0])) + // GIVEN + comparatorByTypes.entrySet().stream() + .forEach(entry -> recursiveComparisonConfiguration.registerComparatorForType(entry.getValue(), + entry.getKey())); + // THEN + assertThat(actual).usingRecursiveComparison(recursiveComparisonConfiguration) .isEqualTo(expected); - } @SuppressWarnings("unused") @@ -83,11 +86,11 @@ private static Stream recursivelyEqualObjectsWhenUsingTypeComparators MapEntry, Comparator> intComparator = entry(Integer.class, new AlwaysEqualComparator()); MapEntry, Comparator> personComparator = entry(Person.class, new AlwaysEqualComparator()); return Stream.of(arguments(person1, person2, mapOf(stringComparator, intComparator), - "same data except int fields and case for strings"), + "same data except int fields and case for strings"), arguments(person3, person4, mapOf(intComparator), "same data except for int fields"), // any neighbour differences should be ignored as we compare persons with AlwaysEqualComparator arguments(person5, person6, mapOf(personComparator), - "same data except for persons, person's fields should not be compared recursively except at the root level")); + "same data except for persons, person's fields should not be compared recursively except at the root level")); } @Test @@ -105,9 +108,9 @@ public void should_fail_when_actual_differs_from_expected_when_using_comparators expected.neighbour = new Person("Jack"); expected.neighbour.home.address.number = 123; // register comparators for some type that will fail the comparison - recursiveComparisonConfiguration.registerComparatorForType(Person.class, new AlwaysDifferentComparator<>()); - recursiveComparisonConfiguration.registerComparatorForType(Date.class, new AlwaysDifferentComparator<>()); - recursiveComparisonConfiguration.registerComparatorForType(Address.class, new AlwaysDifferentComparator<>()); + recursiveComparisonConfiguration.registerComparatorForType(new AlwaysDifferentComparator<>(), Person.class); + recursiveComparisonConfiguration.registerComparatorForType(new AlwaysDifferentComparator<>(), Date.class); + recursiveComparisonConfiguration.registerComparatorForType(new AlwaysDifferentComparator<>(), Address.class); // WHEN compareRecursivelyFailsAsExpected(actual, expected); @@ -133,7 +136,7 @@ public void should_be_able_to_compare_objects_recursively_using_some_precision_f // THEN assertThat(goliath).usingRecursiveComparison() - .withComparatorForType(Double.class, new AtPrecisionComparator<>(0.2)) + .withComparatorForType(new AtPrecisionComparator<>(0.2), Double.class) .isEqualTo(goliathTwin); } @@ -144,7 +147,7 @@ public void should_handle_null_field_with_type_comparator() { Patient expected = new Patient(new Timestamp(3L)); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForType(Timestamp.class, ALWAY_EQUALS_TIMESTAMP) + .withComparatorForType(ALWAY_EQUALS_TIMESTAMP, Timestamp.class) .isEqualTo(expected); } @@ -156,7 +159,7 @@ public void should_ignore_comparators_when_fields_are_the_same() { Patient expected = new Patient(dateOfBirth); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForType(Timestamp.class, NEVER_EQUALS) + .withComparatorForType(NEVER_EQUALS, Timestamp.class) .isEqualTo(expected); } @@ -169,10 +172,10 @@ public void should_treat_timestamp_as_equal_to_date_when_registering_a_Date_symm expected.dateOfBirth = new Date(1000L); // THEN assertThat(actual).usingRecursiveComparison() - .withComparatorForType(Timestamp.class, SYMMETRIC_DATE_COMPARATOR) + .withComparatorForType(SYMMETRIC_DATE_COMPARATOR, Timestamp.class) .isEqualTo(expected); assertThat(expected).usingRecursiveComparison() - .withComparatorForType(Timestamp.class, SYMMETRIC_DATE_COMPARATOR) + .withComparatorForType(SYMMETRIC_DATE_COMPARATOR, Timestamp.class) .isEqualTo(actual); } diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_comparatorByType_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_comparatorByType_Test.java index 88a1cc0f71..70004950c0 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_comparatorByType_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_comparatorByType_Test.java @@ -15,6 +15,8 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.internal.TypeComparators.defaultTypeComparators; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TUPLE; import java.util.Comparator; import java.util.List; @@ -22,7 +24,6 @@ import org.assertj.core.groups.Tuple; import org.assertj.core.internal.TypeComparators; -import org.assertj.core.test.AlwaysEqualComparator; import org.assertj.core.util.AbsValueComparator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -49,11 +50,13 @@ public void should_have_default_comparator_by_types() { public void should_register_given_comparator_per_types() { // GIVEN AbsValueComparator integerComparator = new AbsValueComparator<>(); - recursiveComparisonConfiguration.registerComparatorForType(Integer.class, integerComparator); - recursiveComparisonConfiguration.registerComparatorForType(Tuple.class, AlwaysEqualComparator.ALWAY_EQUALS_TUPLE); + recursiveComparisonConfiguration.registerComparatorForType(integerComparator, Integer.class); + recursiveComparisonConfiguration.registerComparatorForType(ALWAY_EQUALS_TUPLE, Tuple.class); + recursiveComparisonConfiguration.registerComparatorForType(ALWAY_EQUALS, Double.class); // THEN assertThat(recursiveComparisonConfiguration.getComparatorForType(Integer.class)).isSameAs(integerComparator); - assertThat(recursiveComparisonConfiguration.getComparatorForType(Tuple.class)).isSameAs(AlwaysEqualComparator.ALWAY_EQUALS_TUPLE); + assertThat(recursiveComparisonConfiguration.getComparatorForType(Tuple.class)).isSameAs(ALWAY_EQUALS_TUPLE); + assertThat(recursiveComparisonConfiguration.getComparatorForType(Double.class)).isSameAs(ALWAY_EQUALS); } } diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_fieldComparators_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_fieldComparators_Test.java index 9e421071c2..35f6da755b 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_fieldComparators_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_fieldComparators_Test.java @@ -33,8 +33,8 @@ public void setup() { public void should_register_given_field_comparators() { // GIVEN AbsValueComparator integerComparator = new AbsValueComparator<>(); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("height"), integerComparator); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("weight"), ALWAY_EQUALS_TUPLE); + recursiveComparisonConfiguration.registerComparatorForField(integerComparator, fielLocation("height")); + recursiveComparisonConfiguration.registerComparatorForField(ALWAY_EQUALS_TUPLE, fielLocation("weight")); // THEN assertThat(recursiveComparisonConfiguration.getComparatorForField("height")).isSameAs(integerComparator); assertThat(recursiveComparisonConfiguration.getComparatorForField("weight")).isSameAs(ALWAY_EQUALS_TUPLE); @@ -43,8 +43,8 @@ public void should_register_given_field_comparators() { @Test public void should_replace_a_registered_field_comparator() { // GIVEN - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("height"), new AbsValueComparator<>()); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("height"), ALWAY_EQUALS_TUPLE); + recursiveComparisonConfiguration.registerComparatorForField(new AbsValueComparator<>(), fielLocation("height")); + recursiveComparisonConfiguration.registerComparatorForField(ALWAY_EQUALS_TUPLE, fielLocation("height")); // THEN assertThat(recursiveComparisonConfiguration.getComparatorForField("height")).isSameAs(ALWAY_EQUALS_TUPLE); } diff --git a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_multiLineDescription_Test.java b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_multiLineDescription_Test.java index 6d08288444..b994a9c36a 100644 --- a/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_multiLineDescription_Test.java +++ b/src/test/java/org/assertj/core/api/recursive/comparison/RecursiveComparisonConfiguration_multiLineDescription_Test.java @@ -108,8 +108,8 @@ public void should_show_the_ignored_overridden_equals_methods_fields() { @Test public void should_show_the_registered_comparator_by_types_and_the_default_ones() { // WHEN - recursiveComparisonConfiguration.registerComparatorForType(Integer.class, new AbsValueComparator<>()); - recursiveComparisonConfiguration.registerComparatorForType(Tuple.class, AlwaysEqualComparator.ALWAY_EQUALS_TUPLE); + recursiveComparisonConfiguration.registerComparatorForType(new AbsValueComparator<>(), Integer.class); + recursiveComparisonConfiguration.registerComparatorForType(AlwaysEqualComparator.ALWAY_EQUALS_TUPLE, Tuple.class); String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION); // THEN // @format:off @@ -125,9 +125,9 @@ public void should_show_the_registered_comparator_by_types_and_the_default_ones( @Test public void should_show_the_registered_comparator_for_specific_fields_alphabetically() { // WHEN - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("foo"), ALWAY_EQUALS_TUPLE); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("bar"), alwaysDifferent()); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("height"), new PercentageComparator()); + recursiveComparisonConfiguration.registerComparatorForField(ALWAY_EQUALS_TUPLE, fielLocation("foo")); + recursiveComparisonConfiguration.registerComparatorForField(alwaysDifferent(), fielLocation("bar")); + recursiveComparisonConfiguration.registerComparatorForField(new PercentageComparator(), fielLocation("height")); String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION); // THEN // @format:off @@ -165,10 +165,10 @@ 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.registerComparatorForType(Integer.class, new AbsValueComparator<>()); - recursiveComparisonConfiguration.registerComparatorForType(Tuple.class, AlwaysEqualComparator.ALWAY_EQUALS_TUPLE); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("foo"), ALWAY_EQUALS_TUPLE); - recursiveComparisonConfiguration.registerComparatorForField(fielLocation("bar.baz"), alwaysDifferent()); + recursiveComparisonConfiguration.registerComparatorForType(new AbsValueComparator<>(), Integer.class); + recursiveComparisonConfiguration.registerComparatorForType(AlwaysEqualComparator.ALWAY_EQUALS_TUPLE, Tuple.class); + recursiveComparisonConfiguration.registerComparatorForField(ALWAY_EQUALS_TUPLE, fielLocation("foo")); + recursiveComparisonConfiguration.registerComparatorForField(alwaysDifferent(), fielLocation("bar.baz")); // WHEN String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION); // THEN