Skip to content

Recursive comparison with custom comparator throws AssertionError when objects are equal #2954

@Soamid

Description

@Soamid

Describe the bug
I needed to combine assertion with a custom comparator and recursive comparison of an unordered collection. At first, it worked like a charm, but in a very specific case, it started to fail. I spent hours debugging it and I think I know what causes that problem. In RecursiveComparisonDifferenceCalculator#initDualValuesToCompare() detected dual values are filtered to remove already visited cases. However, the equals() method of DualValue only checks actual and expected values, ignoring fieldLocation (there is a comment there questioning this btw ;) ). As a result, similar dual values created in different locations (e.g. the same object in different collections) are marked as similar and removed. Then, the collection of differences is empty and then compareUnorderedIterables() wrongly assumes it found a match and removes investigated value from its iterator...

  • assertj core version: 3.24.2
  • java version: 17.0.3
  • test framework version: JUnit 5.8.2

Test case reproducing the bug

It sounds a bit complicated so I highly recommend seeing the example code below and running it with a debugger. Ofc it's not my real case, just a minimal example to reproduce the problem.

It will throw AssertionError with the message: The following expected elements were not matched in the actual List12: [pl.edu.agh.mobint.db.application.statistics.phonenumber.Example$DataStore@7331196b]. If you remove the custom comparator, the test passes.

public class Example {

  class DataStore {
    List<Data> store1 = new ArrayList<>();

    List<Data> store2 = new ArrayList<>();
  }

  class Data {
    private String text;

    Data(String text) {
      this.text = text;
    }

    public String getText() {
      return text;
    }

    @Override
    public String toString() {
      return "Data: " + text;
    }
  }

  @Test
  void assertTest() {
    var dataStore1 = createDataStore(true);
    var dataStore2 = createDataStore(false);

    assertThat(List.of(dataStore1)).usingRecursiveComparison(RecursiveComparisonConfiguration.builder()
            .withComparatorForType(Comparator.comparing(Data::getText), Data.class)
            .build())
        .ignoringCollectionOrder()
        .isEqualTo(List.of(dataStore2));
  }

  private DataStore createDataStore(boolean reverse) {
    var data1 = new Data("123");
    var data2 = new Data("456");
    var data3 = new Data("987");

    var dataStore = new DataStore();
    dataStore.store1.addAll(reverse ? List.of(data3, data2, data1) : List.of(data1, data2, data3));
    dataStore.store2.addAll(reverse ? List.of(data3, data2) : List.of(data2, data3));
    return dataStore;
  }
}

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions