23

I am trying to validate serialize and de-serialize routines by comparing the resulting object with the original object. The routines can serialize arbitrary and deeply nested classes and consequently I want a comparison routine which can be given the original and final instance and reflectively walk through each value type and compare the values and iteratively dive into reference types to compare values.

I have tried the Apache Commons Lang EqualsBuilder.reflectionEquals(inst1, inst2) but this does not appear to do a very deep comparison, it simply compares reference types for equality rather than diving deeper into them:

The following code illustrates my issue. The first call to reflectionEquals returns true but the second returns false.

Is there a library routine anyone could recommend?

class dummy {
    dummy2 nestedClass;
}

class dummy2 {
    int intVal;
}

@Test
public void testRefEqu() {

    dummy inst1 = new dummy();
    inst1.nestedClass = new dummy2();
    inst1.nestedClass.intVal = 2;
    dummy inst2 = new dummy();
    inst2.nestedClass = new dummy2();
    inst2.nestedClass.intVal = 2;
    boolean isEqual = EqualsBuilder.reflectionEquals(inst1.nestedClass, inst2.nestedClass);
    isEqual = EqualsBuilder.reflectionEquals(inst1, inst2);
}
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
Howard May
  • 6,639
  • 9
  • 35
  • 47
  • If reflection equals is just comparing references, then it has a bug. It should do more than that. – DwB Mar 09 '12 at 11:04
  • @DwB I suspect the intent of the code is to allow you to reflectively implement equals() in a specific class. This is different to what I want which is to reflect on two object instances. In this context it isn't a bug but rather a disappointment! – Howard May Mar 09 '12 at 11:11
  • 1
    I lost a half day, of this weak undocumented behavior of EqualsBuilder. If the field of an passed object is an non primitive the builde rjust calls object.equals(). Very disappointing and useless. – AlexWien Mar 03 '14 at 17:36
  • 4
    @AlexWien It's far from useless. In fact, a fully recursive equality method could be quite dangerous if not used correctly! However, I agree the documentation should be clearer. I've raised https://issues.apache.org/jira/browse/LANG-1034 to address the missing functionality. I'll raise another bug to address the misleading documentation. – Duncan Jones Aug 07 '14 at 07:53
  • 1
    @AlexWien I've never seen any method called `equals` doing a deep comparison. A deep comparison seems to be a hardly ever needed exception, so you shouldn't be disappointed by a standard behavior. – maaartinus Aug 10 '14 at 18:36
  • @maartinus an (custom) equals methods has to check the objects for perfect equality: depending on the use case a custom equals must check all nested objects: imagine a line object with two point objects. (The line is euqal when the two points defining the line are equal = nested equal neccessary). If for some stated reasons an auto equals is dangerous, then at least it has to be documented. (Thanks Duncan) – AlexWien Aug 19 '14 at 13:26

5 Answers5

16

From the answer to this question https://stackoverflow.com/a/1449051/116509 and from some preliminary testing, it looks like Unitils' ReflectionAssert.assertReflectionEquals does what you're expecting. (Edit: but may be abandoned, so you could try AssertJ https://assertj.github.io/doc/#assertj-core-recursive-comparison)

2021 edit: EqualsBuilder now has a testRecursive option. However the unit test libraries mentioned will give you a better failure message to help debug, so depending on context, they're still the best option.

artbristol
  • 32,010
  • 5
  • 70
  • 103
  • I share your surprise though to be fair to EqualsBuilder, and as I commented on the question, I think that its a matter of misunderstanding the intent of the routine. Perhaps this is something which could be made more clear. – Howard May Mar 09 '12 at 11:28
  • I've done some initial testing on assertReflectionEquals and it does seem to work. Thanks very much – Howard May Mar 09 '12 at 15:18
  • 1
    @artbristol I've raised https://issues.apache.org/jira/browse/LANG-1035 to address this. I'll try to ensure it's fixed for the next release. – Duncan Jones Aug 07 '14 at 08:00
  • Unitils doesn't seem to be supported any more ([SO](http://stackoverflow.com/a/34659291/125246)). I use [AssertJ's](http://joel-costigliola.github.io/assertj/) [field-by-field comparisons](http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#field-by-field-comparison) instead. – paulcm Nov 13 '16 at 12:34
  • devs need this. Thankyou for the alternatives =) In Rust you would just type `#[derive(PartialEq)]` on your `struct` and then you may test deep equality wherever you want. It is used heavily and successfully for testing purposes. Java would definitely benefit from this by using standard reflection methods too. – nuiun Sep 17 '21 at 08:16
4

One method would be to compare objects using reflection - but this is tricky. Another strategy would be to compare byte arrays of serialized objects:

class dummy implements Serializable {
    dummy2 nestedClass;
}

class dummy2  implements Serializable {
    int intVal;
}

@Test
public void testRefEqu() throws IOException {

    dummy inst1 = new dummy();
    inst1.nestedClass = new dummy2();
    inst1.nestedClass.intVal = 2;

    dummy inst2 = new dummy();
    inst2.nestedClass = new dummy2();
    inst2.nestedClass.intVal = 2;

    boolean isEqual1 = EqualsBuilder.reflectionEquals(inst1.nestedClass, inst2.nestedClass);
    boolean isEqual2 = EqualsBuilder.reflectionEquals(inst1, inst2);

    System.out.println(isEqual1);
    System.out. println(isEqual2);

    ByteArrayOutputStream baos1 =new ByteArrayOutputStream();
    ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
    oos1.writeObject(inst1);
    oos1.close();

    ByteArrayOutputStream baos2 =new ByteArrayOutputStream();
    ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
    oos2.writeObject(inst1);
    oos2.close();

    byte[] arr1 = baos1.toByteArray();
    byte[] arr2 = baos2.toByteArray();

    boolean isEqual3 = Arrays.equals(arr1, arr2);

    System.out.println(isEqual3);

}

Your application serializes and deserializes objects so this approach seems to be the fastest solution (in terms of CPU operations) for your problem.

whysoserious
  • 728
  • 1
  • 6
  • 20
  • Cheers Johnny, I had thought about comparing the serialized form which is a neat approach which works around the shortcomings of EqualsBuilder. That said it won't fully validate the serialization and de-serialization as it doesn't quite confirm that the original unserialized form is the same as the deserialized form. Regards – Howard May Mar 09 '12 at 11:31
  • Hey Howard, could you give an example when such situation might happen? I was sure that one object has exactly one serialized form and byte representation. – whysoserious Mar 09 '12 at 11:45
  • Hi Johnny, remember that the reason for this is to test my buggy code. If my serializer fails to serialize a particular field then comparing the serialized versions will not detect a problem. – Howard May Mar 09 '12 at 14:16
  • @HowardMay I am in the same sitution. This answer remebers me to what I have done long ago. This serialisation testing approach works, because java standard serialisation works. (Or you get an Serialisation Exception, or you had overidden java serialisatoin). – AlexWien Mar 03 '14 at 17:40
3

You can use AssertJ's field by field recursive comparison feature, for example:

import static org.assertj.core.api.BDDAssertions.then;

then(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);
yurez
  • 2,826
  • 1
  • 28
  • 22
0

I understand that this is a late reply, but this might be useful to someone in need. So in my project I was in need to compare the entire set of deserialzied values to see if they matched properly and the dep I used is Javers. It integrates well with SpringBoot and the usage is very straight forward.

Here is the sample impl:

@RunWith(SpringRunner.class)
public class DeserializerTest {

    private HubMessage message;
    private static final String EXPECTED_INVALID_MSG = "testInvalidMessage";
    private PlatformEventHeader msgHeaders;
    private ObjectMapper objectMapper = new ObjectMapper();
    private final DeserializerClass testDeserializerClass = new DeserializerClass();
    private String TOPIC_TEST = "testTopic";
    private static final String ACTUAL_HUBMESSAGE = "XXXStringValueofmessage";
    @Before
    public void stubSetup() throws IOException {
        // setup the message
       message = new HubMessage ();

        //setup the headers
       msgHeaders = new PlatformEventHeader();
        ...
        ...

        DomainEvent event= new DomainEvent();
        Map<String, String> msgEventObjMap = new HashMap<>();
        ...
        event.setMessage(msgEventObjMap);
        message.setEvent(event);
        message.setHeader(msgHeaders);

    }

    @Test
   public void test_deserialize_validMessage() throws JsonProcessingException {
          // My actual deserialzierClass
          HubMessage messageReceived = testDeserializerClass.deserialize(TOPIC_TEST,  
             new RecordHeaders(),ACTUAL_HUBMESSAGE .getBytes());

        Javers javers = JaversBuilder.javers().build();
        Diff diff = javers.compare((Object)objectMapper.convertValue(messageReceived, Object.class),
                (Object)objectMapper.readValue(ACTUAL_HUBMESSAGE, Object.class));
        Assert.assertEquals(diff.getChanges().size() , 0);
    }

I used the Diff to identify if any changes are there in the objects. Now overriding/using toString() is not efficient as it expects the properties tobe in the same order for the expected and actual value in Matchers. Unitils solve this might impose a lot of headache with a lot of third party jars that are downloaded transitively. So a better bet is to use Javers.io

vijayakumarpsg587
  • 1,079
  • 3
  • 22
  • 39
0

Implement the equals() method on the classes in question. The equals of each call will compare the equality of the nested classes (or, if you like, will compare the equality of the data members). A correctly written equals method will always result in a deep compare.

In your example, the dummy class equals would be something like this:

public boolean equals(Object other)
{
    if (other == this) return true;
    if (other instanceOf dummy)
    {
        dummy dummyOther = (dummy)other;
        if (nestedClass == dummyOther.nestedClass)
        {
           return true;
        }
        else if (nestedClass != null)
        {
           return nestedClass.equals(dummyOther);
        }
        else // nestedClass == null and dummyOther.nestedClass != null.
        {
           return false;
        }
    }
    else
    {
      return false;
    }
}
DwB
  • 37,124
  • 11
  • 56
  • 82
  • 3
    I understand this is the normal way of achieving this, and for most situations is the recommended way. The built in mechanism for calculating equality is robust and extensible to allow custom classes to define what equality means for them. Unfortunately my requirements prevent me from being able to implement equals() on all the nested classes and consequently I hope to use reflection. Thanks – Howard May Mar 09 '12 at 11:15
  • 1
    The problem with this approach is that it cannot handle cyclic object graphs. Comparing a cyclic object graphs using this approach would result in infinite recursion. – jhegedus Feb 12 '14 at 08:50
  • I agree that cyclic object graphs will be a problem. as they are with serialization and fail over (which is probably just a symptom of serialization). – DwB Feb 12 '14 at 13:49