Search code examples
javaassertj

Compare 2 objects ignoring null values


The project uses TestNg, Java11, Spring test

I am writing testNG tests for API I have a java object that has this kind of stucture:

class Object1
    private Object2 o2;
    private List<Object3> o3;

Where Object2 is not only composed of primitive attributes.

I would like to test if 2 Object1 are equals with these rules:

  • if actual o2 is null, don't fail even if the other o2 is not
  • if actual o3 is null or empty, don't fail even if the other o3 is not
  • if actual o3 is not null nor empty, compare only non null Object3 fields

To sum up, I would like to assert that 2 objects are the same, ignoring null fields recursively.

I could do it

assertThat(actual).usingRecursiveComparison().ignoringActualNullFields().isEqualTo(other);

but the recursive null fields are not ignored.

How can I fix this?


Solution

  • I finally created my own asserts like this:

    import org.assertj.core.api.AbstractAssert;
    import org.assertj.core.api.Assertions;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class Object1Assert extends AbstractAssert<Object1Assert, Object1> {
    
        public Object1Assert isEqualTo(Object1 other) {
    
            // specially for null
            if(actual == other) {return this;}
    
            if(actual.getObject2() != null) {
                Assertions.assertThat(other.getObject2()).isEqualToIgnoringNullFields(actual.getObject2());
            }
    
            if(actual.getObject3() != null) {
                for(Object3 object3 : actual.getObject3()) {
                    my.package.Assertions.assertThat(object3).isIn(other.getObject3());
                }
            }
            // return the current assertion for method chaining
            return this;
        }
    
        public Object1Assert(Object1 actual) {
            super(actual, Object1Assert.class);
        }
    
        public static Object1Assert assertThat(Object1 actual) {
            return new Object1Assert(actual);
        }
    
    }
    
    
    public class Assertions {
    
        public static Object3Assert assertThat(Object3 actual) {
            return new Object3Assert(actual);
        }
    }
    
    public class Object3Assert extends AbstractAssert<Object3Assert, Object3> {
    
        public Object3Assert isIn(List<Object3> others) {
            List<String> otherStringIds = others.stream().map(Object3::getStringId).collect(Collectors.toList());
            Assertions.assertThat(otherStringIds).isNotEmpty();
            Assertions.assertThat(actual.getStringId()).isIn(otherStringIds);
            for (Object3 otherObject3 : others) {
                if(actual.getStringId().equalsIgnoreCase(otherObject3.getStringId())) {
                    Assertions.assertThat(otherObject3).usingComparatorForType(Comparators.bigDecimalComparator, BigDecimal.class).isEqualToIgnoringNullFields(actual);
                }
            }
            // return the current assertion for method chaining
            return this;
        }
    
        public Object3Assert(Object3 actual) {
            super(actual, Object3Assert.class);
        }
    
        public static Object3Assert assertThat(Object3 actual) {
            return new Object3Assert(actual);
        }
    
    }
    

    I created this class for each type I needed with this tutorial https://www.baeldung.com/assertj-custom-assertion