Search code examples
javajunitlombokassertj

AssertJ error reporting fails due to a circular reference with toString()


Legacy code to be tested has circular references so something like many-to-one bidirectional relationship entities.

Unfortunately there are also Lombok annotations @ToString & @Data in related classes which then result into a stack overflow whenever toString() is called.

Of course I realize that this is a bit bad code and if toString() methods really are needed there should be some agreement and needed fields should be annotated with @ToString.Exclude to prevent circular reference with toString(). However it is not currently up to me to touch this code but I still would like to use AssertJ, for example like:

assertThat(list1).containsExactly(item1, item2);

instead of doing some more complicated testing code.

The problem now is that instead of AssertJ reporting assertion failure the test execution fails because this circular reference when AssertJ calls toString() on items to report what were the failing items.

Is it possible and if then how can I change AssertJ to report failures without calling the toString() but - for example - getId() on failing items?


Solution

  • One solution would be to use a Custom representation for the lists: Assuming the class Obj which has circular reference to it's toString(). Define a custom representation class as:

    class CustomListRepresentation implements Representation {
        public static final CustomListRepresentation instance = new CustomListRepresentation();
        
        private CustomListRepresentation() {
        }
        @Override
        public String toStringOf(Object) {
            List<?> list = (List<?>) object;
            return list.stream()
                       .filter(Obj.class::isInstance)
                       .map(Obj.class::cast).map(Obj::getId)
                       .map(String::valueOf)
                       .collect(Collectors.joining(","));
        }
    
        @Override
        public String unambiguousToStringOf(Object object) {
            return this.toStringOf(object);
        }
    
    }
    

    If multiple classes other than Obj also have this issue then you could keep a map of these classes along with their mapping functions (to be used instead of toString()) can be defined and replace the .filter(Obj.class::isInstance).map(Obj.class::cast).map(Obj::getId) appropriately.

    Now the assert statement need to be modified as:

    assertThat(list).withRepresentation(CustomListRepresentation.instance)
                    .containsExactly(item1, item2);
    

    Or a different way would be to override the error message using the overridingErrorMessage method:

    private static String getError(List<Obj> list, Obj... items) {
        // return a custom formatted error message.....
    }
    

    Assert as:

    assertThat(list).overridingErrorMessage(getError(list, item1, item2))
                    .containsExactly(item1, item2);