Assertj-core: assertThat(actual).usingRecursiveComparison().ignoringAllOverriddenEquals().isEqualTo(expected) does not verify fields in a parent class

Created on 28 Mar 2020  路  7Comments  路  Source: assertj/assertj-core

Summary

I've come across an interesting behavior of recursive comparison in assertj library. If you compare objects of classes that are sub-classes, fields of super classes seem to be skipped during the comparison. Is that a known issue? Or am I doing something wrong? Here is a brief example:

Example

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

public class ExampleTest {

  @Test
  public void test() {
    MyException expected =
            new MyException("Expected text message");
    MyException actual = new MyException("Actual text message"); //values for the field Throwable.detailMessage are different
    assertThat(actual).usingRecursiveComparison().ignoringAllOverriddenEquals().isEqualTo(expected);
  }
}

class MyException extends RuntimeException {

  MyException(String message) {
    super(message);
  }
}

This test will pass when actually it should not as actual.getMessage() and expected.getMessage() will return different values.

All 7 comments

Thanks for reporting this, I'm going to have a look at it shortly.

Thanks for the prompt response.
Seems I've found the reason. Apparently, the lib skips comparison of the fields that are inherited from superclasses that reside in java.lang. As MyException is using a detailMessage field inherited from java.lang.Throwable, it is skipped. Here is the code from org.assertj.core.internal.Objects that seems to be responsible for such a behavior:

/**
   * Returns the declared fields of given class and its superclasses stopping at superclass in <code>java.lang</code>
   * package whose fields are not included.
   *
   * @param clazz the class we want the declared fields.
   * @return the declared fields of given class and its superclasses.
   */
public static Set<Field> getDeclaredFieldsIncludingInherited(Class<?> clazz) {
    checkNotNull(clazz, "expecting Class parameter not to be null");
    Set<Field> declaredFields = getDeclaredFieldsIgnoringSyntheticAndStatic(clazz);
    // get fields declared in superclass
    Class<?> superclazz = clazz.getSuperclass();
    while (superclazz != null && !superclazz.getName().startsWith("java.lang")) {
      declaredFields.addAll(getDeclaredFieldsIgnoringSyntheticAndStatic(superclazz));
      superclazz = superclazz.getSuperclass();
    }
    return declaredFields;
  }

May I ask why there is such a limitation?

Awesome investigation @VasilyV, appreciated.

The reason was to avoid base types to be compared field by field, at some point you have to compare stuff normally.

In your case if we did not do that we would for example compare Throwable.backtrace fields which I think is not relevant (at least for most users).

@joel-costigliola Thank you for the nice explanation!

You can always use a specific comparator for a given type or field if you need to, see https://assertj.github.io/doc/#assertj-core-recursive-comparison-comparators

See also #1224 (seems to be a duplicate)

Closing since an explanation has been given.

Was this page helpful?
0 / 5 - 0 ratings