4

Learning how to write unit tests with NUnit.

Struggling to compare two complex objects.

There is an answer to a very similar question here Comparing Two objects using Assert.AreEqual() though it looks like you're expected to override Equals() on your objects - this isn't ideal given how many objects there could be, that you would like to compare, let alone the number of properties that could exist on the objects and their nested objects.

Given the sample object:

   public class AMockObject
   {
       public int Id { get; set; }
       public ICollection<int> Numbers { get; set; }

       public AMockObject()
       {
          Numbers = new List<int>();
       }           
    }

I would like to compare that two separate instances of this object have the same values and am finding Assert.AreEqual() isn't really doing what I expected.

For example, all of these fail:

// Example 1
AMockObject a = new AMockObject();
AMockObject b = new AMockObject();
Assert.AreEqual(a,b); // Fails - they're not equal

// Example 2
AMockObject a = new AMockObject() { Id = 1 };
AMockObject b = new AMockObject() { Id = 1 };
Assert.AreEqual(a, b); // Also fails

// Example 3
AMockObject a = new AMockObject() { Id = 1 };
a.Numbers.Add(1);
a.Numbers.Add(2);
a.Numbers.Add(3);    
AMockObject b = new AMockObject() { Id = 1 };
b.Numbers.Add(1);
b.Numbers.Add(2);
b.Numbers.Add(3);    
Assert.AreEqual(a, b); // also fails

We have code in place where we're cloning various objects and some of them a very large. Given this is a pretty common thing to do is there an equally common way to test that two objects are the same at the property-value level?

The example here has two properties. In the real world I have an object with a couple dozen properties, some of which are lists of other complex objects.

For the time being, I am serializing the objects and comparing the strings though this feels less than ideal.

Darren Wainwright
  • 30,247
  • 21
  • 76
  • 127
  • I think your serialization approach probably isn't that bad. Otherwise you are looking at overriding the equality stuff, or writing some complicated reflection thing that goes deep. – Derek Jun 29 '17 at 13:00
  • You need to consider for the lists, is 1, 2, 3 the same as 3,1,2 ? Does the ordering matter? Xml comparison would probably end up different. – Derek Jun 29 '17 at 13:02
  • @Derek - For the example, the order may not matter. Though for being a true comparison it just might. In my real world case it doesn't, but that's not to say it won't. – Darren Wainwright Jun 29 '17 at 13:04

4 Answers4

6

There is a tool used in unit testing called Fluent Assertions which is capable of doing such comparisons.

Note however

Objects are equivalent when both object graphs have equally named properties with the same value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another and the result is equal. The type of a collection property is ignored as long as the collection implements System.Collections.IEnumerable and all items in the collection are structurally equal. Notice that actual behavior is determined by the global defaults managed by FluentAssertions.AssertionOptions.

using FluentAssertions;

//...

// Example 1
AMockObject a = new AMockObject();
AMockObject b = new AMockObject();
a.ShouldBeEquivalentTo(b); // Asserts that an object is equivalent to another object.

// Example 2
AMockObject a = new AMockObject() { Id = 1 };
AMockObject b = new AMockObject() { Id = 1 };
a.ShouldBeEquivalentTo(b); //Asserts that an object is equivalent to another object.

// Example 3
AMockObject a = new AMockObject() { Id = 1 };
a.Numbers.Add(1);
a.Numbers.Add(2);
a.Numbers.Add(3);    
AMockObject b = new AMockObject() { Id = 1 };
b.Numbers.Add(1);
b.Numbers.Add(2);
b.Numbers.Add(3);
a.ShouldBeEquivalentTo(b)    
a.Numbers.ShouldAllBeEquivalentTo(b.Numbers); // Asserts that a collection of objects is equivalent to another collection of objects.

Documentation here

thebenman
  • 1,621
  • 14
  • 35
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • This looks super interesting! Thanks :) - I haven't dived into their github though wonder if they're serializing the object somehow, given the types being ignored (not an issue in my case) – Darren Wainwright Jun 29 '17 at 13:03
  • Thank you once again NKosi. We're also kicking the tires of BDD-style test cases, so this slots in just nicely. – Darren Wainwright Jun 29 '17 at 13:13
1

Serialize it to Json and compare

Using Newtonsoft.Json.JsonConverter, just do it.

var expected = JsonConvert.SerializeObject(a);
var result = JsonConvert.SerializeObject(b);

Assert.AreEqual(expected, result);
0

Since nothing else is changing you could simply compare the values of the properties of the created objects?

marto
  • 420
  • 1
  • 4
  • 15
  • The example has 2 properties. In real life the object I want to compare has more than 20 properties including lists that contain other objects with many properties - think a graph. – Darren Wainwright Jun 29 '17 at 12:49
  • In my modest experience I did it this way (no matter the number & complexity of the object's properties, without modifying equals and gethashvalue). You could assert that they are instance of the same class (if that makes sense in your scenario) and then check manually the values against predefined expected answers. – marto Jun 29 '17 at 12:59
0

You could use reflection (see get fields with reflection ) and iterate through the fields in both classes. FieldInfo.GetValue() should get you the values to compare.

Surak of Vulcan
  • 348
  • 2
  • 11
  • I'm assuming you have used reflection? There is a lot of code to write resulting in a larger chance of erroneous code. I'd rather not have to write a unit test to test my unit test :) – Darren Wainwright Jun 29 '17 at 13:02
  • I agree it would not be straightforward, but it is a way to make a one-compare-to-rule-them-all template. It appears you are looking for less generality, so this is probably an overkill. – Surak of Vulcan Jun 29 '17 at 13:18
  • A complete perfect comparison is what I would ideally like and will eventually need. Just surprising that there isn't something built in to any of these leading test-frameworks right out of the box. – Darren Wainwright Jun 29 '17 at 13:24