All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.assertj.api.AbstractObjectAssert Maven / Gradle / Ivy

The newest version!
/**
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * Copyright 2012-2016 the original author or authors.
 */
package com.fitbur.assertj.api;

import static com.fitbur.assertj.extractor.Extractors.byName;

import java.util.function.Function;
import java.util.stream.Stream;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import com.fitbur.assertj.groups.Tuple;
import com.fitbur.assertj.util.DoubleComparator;
import com.fitbur.assertj.util.FloatComparator;
import com.fitbur.assertj.util.introspection.IntrospectionError;

/**
 * Base class for all implementations of assertions for {@link Object}s.
 * 
 * @param  the "self" type of this assertion class. Please read "Emulating 'self types' using Java Generics to simplify fluent API implementation"
 *          for more details.
 * @param  the type of the "actual" value.
 * 
 * @author Yvonne Wang
 * @author Alex Ruiz
 * @author Nicolas François
 * @author Mikhail Mazursky
 * @author Joel Costigliola
 * @author Libor Ondrusek
 */
public abstract class AbstractObjectAssert, A> extends AbstractAssert {

  private static final double DOUBLE_COMPARATOR_PRECISION = 1e-15;
  private static final float FLOAT_COMPARATOR_PRECISION = 1e-6f;

  private Map> comparatorByPropertyOrField = new HashMap<>();
  private Map, Comparator> comparatorByType = defaultTypeComparators();

  protected AbstractObjectAssert(A actual, Class selfType) {
    super(actual, selfType);
  }

  public static Map, Comparator> defaultTypeComparators() {
    Map, Comparator> comparatorByType = new HashMap<>();
    comparatorByType.put(Double.class, new DoubleComparator(DOUBLE_COMPARATOR_PRECISION));
    comparatorByType.put(Float.class, new FloatComparator(FLOAT_COMPARATOR_PRECISION));
    return comparatorByType;
  }

  /**
   * Assert that the actual object is equal to the given one by comparing actual's properties/fields with other's
   * not null properties/fields only (including inherited ones).
   * 

* It means that if an actual field is not null and the corresponding field in other is null, this field will be * ignored in comparison, but the opposite will make assertion fail (null field in actual, not null in other) as * the field is used in the performed comparison and the values differ. *

* Note that comparison is not recursive, if one of the field is an Object, it will be compared to the other * field using its equals method. *

* If an object has a field and a property with the same name, the property value will be used over the field. *

* Private fields are used in comparison but this can be disabled using * {@link Assertions#setAllowComparingPrivateFields(boolean)}, if disabled only accessible fields values are * compared, accessible fields include directly accessible fields (e.g. public) or fields with an accessible getter. *

* The objects to compare can be of different types but the properties/fields used in comparison must exist in both, * for example if actual object has a name String field, it is expected other object to also have one. *

* * Example: *

 TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * TolkienCharacter mysteriousHobbit = new TolkienCharacter(null, 33, HOBBIT);
   * 
   * // Null fields in other/expected object are ignored, the mysteriousHobbit has null name thus name is ignored
   * assertThat(frodo).isEqualToIgnoringNullFields(mysteriousHobbit); // OK
   * 
   * // ... but this is not reversible !
   * assertThat(mysteriousHobbit).isEqualToIgnoringNullFields(frodo); // FAIL
* * @param other the object to compare {@code actual} to. * @throws NullPointerException if the actual or other object is {@code null}. * @throws AssertionError if the actual and the given object are not lenient equals. * @throws IntrospectionError if one of actual's field to compare can't be found in the other object. */ public S isEqualToIgnoringNullFields(Object other) { objects.assertIsEqualToIgnoringNullFields(info, actual, other, comparatorByPropertyOrField, comparatorByType); return myself; } /** * Assert that the actual object is equal to the given one using a property/field by property/field comparison on the given properties/fields only * (fields can be inherited fields or nested fields). This can be handy if equals implementation of objects to compare does not suit you. *

* Note that comparison is not recursive, if one of the field is an Object, it will be compared to the other * field using its equals method. *

* If an object has a field and a property with the same name, the property value will be used over the field. *

* Private fields are used in comparison but this can be disabled using * {@link Assertions#setAllowComparingPrivateFields(boolean)}, if disabled only accessible fields values are * compared, accessible fields include directly accessible fields (e.g. public) or fields with an accessible getter. *

* The objects to compare can be of different types but the properties/fields used in comparison must exist in both, * for example if actual object has a name String field, it is expected the other object to also have one. *

* * Example: *

 TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * TolkienCharacter sam = new TolkienCharacter("Sam", 38, HOBBIT);
   * 
   * // frodo and sam both are hobbits, so they are equals when comparing only race
   * assertThat(frodo).isEqualToComparingOnlyGivenFields(sam, "race"); // OK
   * 
   * // they are also equals when comparing only race name (nested field).
   * assertThat(frodo).isEqualToComparingOnlyGivenFields(sam, "race.name"); // OK
   * 
   * // ... but not when comparing both name and race
   * assertThat(frodo).isEqualToComparingOnlyGivenFields(sam, "name", "race"); // FAIL
* * @param other the object to compare {@code actual} to. * @param propertiesOrFieldsUsedInComparison properties/fields used in comparison. * @throws NullPointerException if the actual or other is {@code null}. * @throws AssertionError if the actual and the given objects are not equals property/field by property/field on given fields. * @throws IntrospectionError if one of actual's property/field to compare can't be found in the other object. * @throws IntrospectionError if a property/field does not exist in actual. */ public S isEqualToComparingOnlyGivenFields(Object other, String... propertiesOrFieldsUsedInComparison) { objects.assertIsEqualToComparingOnlyGivenFields(info, actual, other, comparatorByPropertyOrField, comparatorByType, propertiesOrFieldsUsedInComparison); return myself; } /** * Assert that the actual object is equal to the given one by comparing their properties/fields except for the given ones * (inherited ones are taken into account). This can be handy if equals implementation of objects to compare does not suit you. *

* Note that comparison is not recursive, if one of the property/field is an Object, it will be compared to the other * field using its equals method. *

* If an object has a field and a property with the same name, the property value will be used over the field. *

* Private fields are used in comparison but this can be disabled using * {@link Assertions#setAllowComparingPrivateFields(boolean)}, if disabled only accessible fields values are * compared, accessible fields include directly accessible fields (e.g. public) or fields with an accessible getter. *

* The objects to compare can be of different types but the properties/fields used in comparison must exist in both, * for example if actual object has a name String field, it is expected the other object to also have one. *

* * Example: *

 TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * TolkienCharacter sam = new TolkienCharacter("Sam", 38, HOBBIT);
   * 
   * // frodo and sam are equals when ignoring name and age since the only remaining field is race which they share as HOBBIT.
   * assertThat(frodo).isEqualToIgnoringGivenFields(sam, "name", "age"); // OK
   * 
   * // ... but they are not equals if only age is ignored as their names differ.
   * assertThat(frodo).isEqualToIgnoringGivenFields(sam, "age"); // FAIL
* * @param other the object to compare {@code actual} to. * @param propertiesOrFieldsToIgnore ignored properties/fields to ignore in comparison. * @throws NullPointerException if the actual or given object is {@code null}. * @throws AssertionError if the actual and the given objects are not equals property/field by property/field after ignoring given fields. * @throws IntrospectionError if one of actual's property/field to compare can't be found in the other object. */ public S isEqualToIgnoringGivenFields(Object other, String... propertiesOrFieldsToIgnore) { objects.assertIsEqualToIgnoringGivenFields(info, actual, other, comparatorByPropertyOrField, comparatorByType, propertiesOrFieldsToIgnore); return myself; } /** * Assert that actual object is equal to the given object based on a property/field by property/field comparison (including * inherited ones). This can be handy if equals implementation of objects to compare does not suit you. *

* Note that comparison is not recursive, if one of the field is an Object, it will be compared to the other * field using its equals method. *

* If an object has a field and a property with the same name, the property value will be used over the field. *

* Private fields are used in comparison but this can be disabled using * {@link Assertions#setAllowComparingPrivateFields(boolean)}, if disabled only accessible fields values are * compared, accessible fields include directly accessible fields (e.g. public) or fields with an accessible getter. *

* The objects to compare can be of different types but the properties/fields used in comparison must exist in both, * for example if actual object has a name String field, it is expected the other object to also have one. *

* * Example: *

 // equals not overridden in TolkienCharacter 
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * TolkienCharacter frodoClone = new TolkienCharacter("Frodo", 33, HOBBIT);
   * 
   * // Fail as equals compares object references
   * assertThat(frodo).isEqualsTo(frodoClone);
   * 
   * // frodo and frodoClone are equals when doing a field by field comparison.
   * assertThat(frodo).isEqualToComparingFieldByField(frodoClone);
* * @param other the object to compare {@code actual} to. * @throws AssertionError if the actual object is {@code null}. * @throws AssertionError if the actual and the given objects are not equals property/field by property/field. * @throws IntrospectionError if one of actual's property/field to compare can't be found in the other object. */ public S isEqualToComparingFieldByField(Object other) { objects.assertIsEqualToIgnoringGivenFields(info, actual, other, comparatorByPropertyOrField, comparatorByType); return myself; } /** * Allows to set a specific comparator to compare properties or fields with the given names. * A typical usage is for comparing double/float fields with a given precision. *

* Comparators specified by this method have precedence over comparators added by {@link #usingComparatorForType}. *

* Example: *

*

 public class TolkienCharacter {
   *   private String name;
   *   private double height;
   *   // constructor omitted
   * }
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
   * TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
   * TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
   * 
   * Comparator<Double> closeEnough = new Comparator<Double>() {
   *   double precision = 0.5;
   *   public int compare(Double d1, Double d2) {
   *     return Math.abs(d1 - d2) <= precision ? 0 : 1;
   *   }
   * };
   * 
   * // assertions will pass
   * assertThat(frodo).usingComparatorForFields(closeEnough, "height")
   *                  .isEqualToComparingFieldByField(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForFields(closeEnough, "height")
   *                  .isEqualToIgnoringNullFields(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForFields(closeEnough, "height")
   *                  .isEqualToIgnoringGivenFields(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForFields(closeEnough, "height")
   *                  .isEqualToComparingOnlyGivenFields(tallerFrodo);
   *                  
   * // assertion will fail
   * assertThat(frodo).usingComparatorForFields(closeEnough, "height")
   *                  .isEqualToComparingFieldByField(reallyTallFrodo);
*

* @param comparator the {@link java.util.Comparator} to use * @param comparator the names of the properties and/or fields the comparator should be used for * @return {@code this} assertions object */ public S usingComparatorForFields(Comparator comparator, String... propertiesOrFields) { for (String propertyOrField : propertiesOrFields) { comparatorByPropertyOrField.put(propertyOrField, comparator); } return myself; } /** * Allows to set a specific comparator to compare properties or fields with the given type. * A typical usage is for comparing fields of numeric type at a given precision. *

* Comparators specified by {@link #usingComparatorForFields} have precedence over comparators specified by this method. *

* Example: *

 public class TolkienCharacter {
   *   private String name;
   *   private double height;
   *   // constructor omitted
   * }
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
   * TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
   * TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
   * 
   * Comparator<Double> closeEnough = new Comparator<Double>() {
   *   double precision = 0.5;
   *   public int compare(Double d1, Double d2) {
   *     return Math.abs(d1 - d2) <= precision ? 0 : 1;
   *   }
   * };
   * 
   * // assertions will pass
   * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
   *                  .isEqualToComparingFieldByField(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
   *                  .isEqualToIgnoringNullFields(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
   *                  .isEqualToIgnoringGivenFields(tallerFrodo);
   *                  
   * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
   *                  .isEqualToComparingOnlyGivenFields(tallerFrodo);
   *                  
   * // assertion will fail
   * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
   *                  .isEqualToComparingFieldByField(reallyTallFrodo);
*

* @param comparator the {@link java.util.Comparator} to use * @param comparator the {@link java.lang.Class} of the type the comparator should be used for * @return {@code this} assertions object */ public S usingComparatorForType(Comparator comparator, Class type) { comparatorByType.put(type, comparator); return myself; } /** * Assert that the actual object has the specified field or property. *

* Private fields are matched by default but this can be changed by calling {@link Assertions#setAllowExtractingPrivateFields(boolean) Assertions.setAllowExtractingPrivateFields(false)}. *

* * Example: *

 public class TolkienCharacter {
   *
   *   private String name;
   *   private int age;
   *   // constructor omitted
   *      
   *   public String getName() {
   *     return this.name;
   *   }
   * }
   *
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33);
   * 
   * // assertions will pass :
   * assertThat(frodo).hasFieldOrProperty("name")
   *                  .hasFieldOrProperty("age"); // private field are matched by default
   *         
   * // assertions will fail :
   * assertThat(frodo).hasFieldOrProperty("not_exists");
   * assertThat(frodo).hasFieldOrProperty(null);
   * // disable looking for private fields
   * Assertions.setAllowExtractingPrivateFields(false);
   * assertThat(frodo).hasFieldOrProperty("age"); 
* * @param name the field/property name to check * @throws AssertionError if the actual object is {@code null}. * @throws IllegalArgumentException if name is {@code null}. * @throws AssertionError if the actual object has not the given field/property */ public S hasFieldOrProperty(String name) { objects.assertHasFieldOrProperty(info, actual, name); return myself; } /** * Assert that the actual object has the specified field or property with the given value. *

* Private fields are matched by default but this can be changed by calling {@link Assertions#setAllowExtractingPrivateFields(boolean) Assertions.setAllowExtractingPrivateFields(false)}. *

* * Example: *

 public class TolkienCharacter {
   *   private String name;
   *   private int age;
   *   // constructor omitted
   *
   *   public String getName() {
   *     return this.name;
   *   }
   * }
   *
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33);
   * TolkienCharacter noname = new TolkienCharacter(null, 33);
   *
   * // assertions will pass :
   * assertThat(frodo).hasFieldOrProperty("name", "Frodo");
   * assertThat(frodo).hasFieldOrProperty("age", 33);
   * assertThat(noname).hasFieldOrProperty("name", null);
   *
   * // assertions will fail :
   * assertThat(frodo).hasFieldOrProperty("name", "not_equals"); 
   * assertThat(frodo).hasFieldOrProperty(null, 33);             
   * assertThat(frodo).hasFieldOrProperty("age", null);          
   * assertThat(noname).hasFieldOrProperty("name", "Frodo");
   * // disable extracting private fields
   * Assertions.setAllowExtractingPrivateFields(false);
   * assertThat(frodo).hasFieldOrProperty("age", 33); 
* * @param name the field/property name to check * @param value the field/property expected value * @throws AssertionError if the actual object is {@code null}. * @throws IllegalArgumentException if name is {@code null}. * @throws AssertionError if the actual object has not the given field/property * @throws AssertionError if the actual object has the given field/property but not with the expected value * * @see AbstractObjectAssert#hasFieldOrProperty(java.lang.String) */ public S hasFieldOrPropertyWithValue(String name, Object value) { objects.assertHasFieldOrPropertyWithValue(info, actual, name, value); return myself; } /** * Extract the values of given fields/properties from the object under test into an array, this new array becoming * the object under test. *

* If you extract "id", "name" and "email" fields/properties then the array will contain the id, name and email values * of the object under test, you can then perform array assertions on the extracted values. *

* Nested fields/properties are supported, specifying "adress.street.number" is equivalent to get the value * corresponding to actual.getAdress().getStreet().getNumber() *

* Private fields can be extracted unless you call {@link Assertions#setAllowExtractingPrivateFields(boolean) Assertions.setAllowExtractingPrivateFields(false)}. *

* Example: *

 // Create frodo, setting its name, age and Race (Race having a name property)
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * 
   * // let's verify Frodo's name, age and race name:
   * assertThat(frodo).extracting("name", "age", "race.name")
   *                  .containsExactly("Frodo", 33, "Hobbit");
* * A property with the given name is looked for first, if it doesn't exist then a field with the given name is looked * for, if the field is not accessible (i.e. does not exist) an IntrospectionError is thrown. *

* Note that the order of extracted property/field values is consistent with the iteration order of the array under * test. * * @param propertiesOrFields the properties/fields to extract from the initial array under test * @return a new assertion object whose object under test is the array containing the extracted properties/fields values * @throws IntrospectionError if one of the given name does not match a field or property */ public AbstractObjectArrayAssert extracting(String... propertiesOrFields) { Tuple values = byName(propertiesOrFields).extract(actual); return new ObjectArrayAssert(values.toArray()); } /** * Use the given {@link Function}s to extract the values from the object under test into an array, this new array becoming * the object under test. *

* If the given {@link Function}s extract the id, name and email values then the array will contain the id, name and email values * of the object under test, you can then perform array assertions on the extracted values. *

* Example: *

 // Create frodo, setting its name, age and Race (Race having a name property)
   * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
   * 
   * // let's verify Frodo's name, age and race name:
   * assertThat(frodo).extracting(TolkienCharacter::getName, 
   *                              character -> character.age, // public field
   *                              character -> character.getRace().getName())
   *                  .containsExactly("Frodo", 33, "Hobbit");
*

* Note that the order of extracted values is consistent with the iteration order of the array under test. * * @param extractors the extractor functions to extract a value from an element of the Iterable under test. * @return a new assertion object whose object under test is the array containing the extracted values */ @SafeVarargs public final AbstractObjectArrayAssert extracting(Function... extractors) { Object[] values = Stream.of(extractors) .map(extractor -> extractor.apply(actual)) .toArray(); return new ObjectArrayAssert(values); } /** * Assert that the object under test (actual) is equal to the given object based on recursive a property/field by property/field comparison (including * inherited ones). This can be useful if actual's equals implementation does not suit you. * The recursive property/field comparison is not applied on fields having a custom equals implementation, i.e. * the overriden equals method will be used instead of a field by field comparison. *

* The recursive comparison handles cycle. By default floats are compared with a precision of 1.0E-6 and doubles with 1.0E-15. *

* You can specify a custom comparator per (nested) fields or type with respectively {@link #usingComparatorForFields(Comparator, String...) usingComparatorForFields(Comparator, String...)} * and {@link #usingComparatorForType(Comparator, Class)}. *

* 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. *

* * Example: *

 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 
   * }
   *
   * public class Home {
   *   public Address address = new Address();
   * }
   *
   * public static class Address {
   *   public int number = 1;
   * }
   * 
   * 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).isEqualsTo(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);
   * 
   * // this succeeds because we allow a 0.5 tolerance on double 
   * assertThat(jack).usingComparatorForType(new DoubleComparator(0.5), Double.class)
   *                 .isEqualToComparingFieldByFieldRecursively(jackClone);
   *                 
   * // you can set a comparator on specific fields (nested fields are supported)
   * assertThat(jack).usingComparatorForFields(new DoubleComparator(0.5), "height")
   *                 .isEqualToComparingFieldByFieldRecursively(jackClone);
* * @param other the object to compare {@code actual} to. * @throws AssertionError if the actual object is {@code null}. * @throws AssertionError if the actual and the given objects are not deeply equal property/field by property/field. * @throws IntrospectionError if one property/field to compare can not be found. */ public S isEqualToComparingFieldByFieldRecursively(Object other) { objects.assertIsEqualToComparingFieldByFieldRecursively(info, actual, other, comparatorByPropertyOrField, comparatorByType); return myself; } }