Compare object trees

Aug 23, 2010 at 12:28 PM

I want to compare two hirarchical objects (of the same type). Here's the code:


         MailMessage mailMessage = new MailMessage();
         mailMessage.From = "me";
         mailMessage.To = "you";
         mailMessage.Attachment = new Attachment() { Data = "gifs" }; // (x)

         Likeness<MailMessageMailMessage> copied = new Likeness<MailMessageMailMessage>(mailMessage);
        bool areEqual = copied.Equals(expectedMailMessage);

If I don't use line x it works. What can I do?

Thanks in advance!

 

 

Coordinator
Aug 23, 2010 at 1:43 PM

Thanks for using AutoFixture :)

By default Likeness iterates over all public properties of the destination and finds a similarly named property on the source; it then invokes the destination property's Equals method. As such, it doesn't do nested comparisons, but only flat comparisons by default. This is what you are experiencing here: the Attachment class most likely doesn't override Equals, so when you compare two Attachment instances, Equals returns false.

You can tell Likeness to treat the Attachment property in a special way, for example by comparing instances using a new Likeness. Something like this ought to work:

var copied = new Likeness<MailMessage, MailMessage>(mailMessage)
    .With(d => d.Attachment)
    .EqualsWhen((s, d) => new Likeness<Attachment, Attachment>(s.Attachment).Equals(d.Attachment));

You may have to repeat this for other complex properties, or even for the Attachment type itself if it has nested complex properties.

HTH

Aug 23, 2010 at 3:40 PM

Thanks for the fast answer!

Maybe an automatism (see Rhino Mocks Property.AllPropertiesMatch(mailMessage).Eval(expectedMailMessage); ) would be a great enhancement.

The code would be a lot easier to write (in many cases)!

 

Best regards

Carsten

Coordinator
Aug 23, 2010 at 5:32 PM

Yes, it would be nice to have such a thing, and its not for want of will that it doesn't yet exist. The big question that I've yet to find a satisfactory answer to is: how would it work?

It sounds pretty easy to state that we want recursive Likeness. However, consider a class like this:

public class Foo
{
    public int Number { get; set; }

    public Bar Bar { get; set; }
}

For a recursive Likeness we would like to replace the default comparison for the Bar property with a Likeness, since Bar is a complex type. However, we would like to keep normal equality comparison for the Number property, since Int32 already has a satisfactory implementation of Equals. That leads to the question:

How do we decide which properties will be compared using Likeness, and which ones should use their own Equals implementation?

We can't rely on a positive list because you can always define a Value Object that Likeness knows nothing about - I do that all the time when I create my own object models.

I haven't investigated whether it's possible via Reflection to test whether the Equals method is overridden by a type, but even if it is, it might not be the best algorithm. What if the Equals method implements Entity equality semantics (as per Evans' DDD)? This may be the correct implementation from a Domain Model perspective, but not from a unit testing perspective.

I'm not saying it can't be done; all I'm saying is that I've yet to arrive at a satisfactory algorithm for this feature.

Coordinator
Aug 23, 2010 at 5:39 PM
Edited Aug 23, 2010 at 5:40 PM

I spent a little more time thinking about this and came up with this: http://autofixture.codeplex.com/workitem/4202

Aug 8, 2014 at 10:09 PM
@Ploeh: I am having a problem with the example you gave earlier in this thread back on 8/23/2010.

The following example passes when doing a check only on the Address type.

var likeness = org.Address.AsSource().OfLikeness<Address>();
likeness.Without(a => a.BrokenRulesCollection).Without(a => a.Parent).ShouldEqual(org2.Address);

However, when I do this on the org object itself the check fails on the Address object:
       //This Fails on address object, not sure why
        org.AsSource().OfLikeness<OrganizationEdit>()
            .With(d => d.Address)
            .EqualsWhen((a, b) =>
                a.AsSource().OfLikeness<Address>()
                .Without(addr => addr.Parent)
                .Without(addr => addr.BrokenRulesCollection)
                .Equals(b.Address)
                )
            .ShouldEqual(org2);

What I am missing here?

Thanks
Aug 8, 2014 at 10:28 PM
Edited Aug 8, 2014 at 10:29 PM
Nevermind, I'm an idiot and I should read the intellisense more closely.
        org.AsSource().OfLikeness<OrganizationEdit>()
            .With<Address>(a => a.Address)
            .EqualsWhen((a, b) =>
                a.Address.AsSource().OfLikeness<Address>()
                .Without(addr => addr.Parent)
                .Without(addr => addr.BrokenRulesCollection)
                .Equals(b.Address)
                )
            .Without(a => a.BrokenRulesCollection)
            .ShouldEqual(org2);
Coordinator
Aug 9, 2014 at 8:56 AM
OK, it sounds like you figured it out - thank you for posting the update :)

If there's anything we can help with, the best places to ask are now either the Issues section on GitHub, or on Stack Overflow, using the autofixture tag - we don't really use CodePlex any longer...