Search code examples
javaassertj

usingComparatorForType in assertj does not seem to apply to properties of objects that are properties of the object


I want to test equality of two objects but have a certain discretion about the precision of double values that are present on some nested properties of them. usingComparatorForType seems to be an appropriate solution but it doesn't appear to work if my Foo object has a propery of type Bar where Bar.baz is a double which I want this precision discretion to apply to. The example for isEqualToComparingFieldByFieldRecursively doesn't quite address the situation I'm trying to test.

Some sample code

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Comparator;
import java.util.Objects;

import org.junit.Test;

public class ComparatorForTypeTest {

  private static final Comparator<Double> DOUBLE_COMPARATOR = new Comparator<Double>() {
    @Override
    public int compare(Double d1, Double d2) {
      return Math.abs(d1 - d2) <= 0.1 ? 0 : 1;
    }
  };

  class Foo {
    private int id;
    private double baz;
    private Bar bar;

    public Foo(int id, double baz, Bar bar) {
      this.id = id;
      this.baz = baz;
      this.bar = bar;
    }

    public Foo withBar(Bar bar) {
      Foo that = this;
      that.bar = bar;
      return that;
    }

    @Override
    public int hashCode() {
      return Objects.hash(id, baz, bar);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      } else if (obj == null || obj.getClass() != Foo.class) {
        return false;
      }

      Foo that = (Foo) obj;
      return Objects.equals(this.id, that.id)
          && Objects.equals(this.baz, that.baz)
          && Objects.equals(this.bar, that.bar);
    }

    @Override
    public String toString() {
      return String.format("Foo[id=%d, score=%f, bar=%s]", id, baz, bar == null ? null : bar.toString());
    }
  }

  class Bar {
    private int id;
    private double baz;

    public Bar(int id, double baz) {
      this.id = id;
      this.baz = baz;
    }

    @Override
    public int hashCode() {
      return Objects.hash(id, baz);
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      } else if (obj == null || obj.getClass() != Bar.class) {
        return false;
      }

      Bar that = (Bar) obj;
      return Objects.equals(this.id, that.id)
          && Objects.equals(this.baz, that.baz);
    }

    @Override
    public String toString() {
      return String.format("Bar[id=%d, score=%f]", id, baz);
    }
  }

  @Test
  public void itComparesBars() {
    Bar a = new Bar(1, 1.4);
    Bar b = new Bar(1, 1.45);
    Bar c = new Bar(2, 1.4);

    assertThat(a).isNotEqualTo(b);
    assertThat(b).isNotEqualTo(c);
    assertThat(a).isNotEqualTo(c);

    assertThat(a).usingComparatorForType(DOUBLE_COMPARATOR, Double.class).isEqualToComparingFieldByField(b);
  }

  @Test
  public void itComparesFoos() {
    Foo a = new Foo(1, 1.4, null);
    Foo b = new Foo(1, 1.45, null);
    Foo c = new Foo(2, 1.4, null);

    assertThat(a).isNotEqualTo(b);
    assertThat(b).isNotEqualTo(c);
    assertThat(a).isNotEqualTo(c);

    assertThat(a).usingComparatorForType(DOUBLE_COMPARATOR, Double.class).isEqualToComparingFieldByField(b);

    Bar barA = new Bar(1, 1.4);
    Bar barB = new Bar(1, 1.45);

    assertThat(a.withBar(barA)).usingComparatorForType(DOUBLE_COMPARATOR, Double.class).isEqualToComparingFieldByFieldRecursively(b.withBar(barA));
    assertThat(a.withBar(barA)).usingComparatorForType(DOUBLE_COMPARATOR, Double.class).isEqualToComparingFieldByFieldRecursively(b.withBar(barB));
  }
}

In this case, itComparesFoos is where I'm looking to apply this discretion about the precision of doubles.


Solution

  • The problem here is that Bar has an overridden equals method and this is used to compare Bar instances, this is mentioned in the javadoc (but I get that javadoc is not always the best way to discover an API):

    The recursive property/field comparison is not applied on fields having a custom equals implementation, i.e. the overridden equals method will be used instead of a field by field comparison.

    https://github.com/joel-costigliola/assertj-core/issues/1002 is a ticket to revamp the recursive comparison api which has gone wild, it will provide an option to force recursive comparison even if equals was overridden (likely forcingRecursiveComparisonForAll).