Introduction to TestApi – Part 8: Object Comparison API

Series Index

+++

Comparing two objects for equivalence is a relatively common task during test validation. For example, you might test whether a type follows the rules that are required by a particular serializer by saving and loading the object and comparing the two. In a deep object comparison, all the properties and their sub-properties are compared repeatedly until primitives are reached. The .NET Framework provides mechanisms to perform deep comparisons, but requires the types to implement part of the comparison logic (IComparable, .Equals). Obviously some types do not use these mechanisms. This API provides a mechanism to perform a deep comparison of two objects by using reflection, regardless of whether or not they implement IComparable.

TestApi Object Comparison

TestApi provides an API to create compare operations. The API separates the comparison operation into two parts:

  • ObjectGraphFactory: Builds an intermediate representation from the object that describes what needs to be compared.
  • ObjectComparer: Uses a reusable compare mechanism to compare the intermediate representations of the objects, generated by the factory.

image

Example #1

This example demonstrates how to use the object comparer to compare two objects. Assuming we have the following simple class...

 class Person
{
    public Person(string name) 
    { 
        Name = name;
        Children = new Collection<Person>();
    }
    public string Name { get; set; }
    public Collection<Person> Children { get; private set;  }
}

… we can compare two objects using the following code:

 Person p1 = new Person("John");
p1.Children.Add(new Person("Peter"));

p1.Children.Add(new Person("Mary"));
Person p2 = new Person("John");

p2.Children.Add(new Person("Peter"));
// Perform the compare operation

ObjectGraphFactory factory = new PublicPropertyObjectGraphFactory();

ObjectComparer comparer = new ObjectComparer(factory);

Console.WriteLine(
    "Objects p1 and p2 {0}", 
    comparer.Compare(p1, p2) ? "match!" : "do NOT match!");

Execution of this code produces the following output:

 Objects p1 and p2 do NOT match!

Example #2

The following example demonstrates how to obtain the details of where the two objects do not match.

 Person p1 = new Person("John");
p1.Children.Add(new Person("Peter"));

p1.Children.Add(new Person("Mary"));
Person p2 = new Person("John");

p2.Children.Add(new Person("Peter"));
// Perform the compare operation

ObjectGraphFactory factory = new PublicPropertyObjectGraphFactory();

ObjectComparer comparer = new ObjectComparer(factory);

IEnumerable<ObjectComparisonMismatch> m12 = new List<ObjectComparisonMismatch>();

Console.WriteLine(
    "Objects p1 and p2 {0}", 
    comparer.Compare(p1, p2, out m12) ? "match!" : "do NOT match!");
foreach (ObjectComparisonMismatch m in m12)
{
    Console.WriteLine(
        "Nodes '{0}' and '{1}' do not match. Mismatch message: '{2}'",
        m.LeftObjectNode != null ? m.LeftObjectNode.Name : "null",
        m.RightObjectNode != null ? m.LeftObjectNode.Name : "null",
        m.MismatchType); 
}

Execution of this code produces the following output:

 Objects p1 and p2 do NOT match!
Nodes 'Children' and 'Children' do not match. Mismatch message: 'RightNodeHasFewerChildren'

Nodes 'IEnumerable1' and 'null' do not match. Mismatch message: 'MissingRightNode'

Nodes 'Children' and 'null' do not match. Mismatch message: 'MissingRightNode'

Nodes 'Count' and 'null' do not match. Mismatch message: 'MissingRightNode'

Nodes 'Name' and 'null' do not match. Mismatch message: 'MissingRightNode'

Nodes 'Count' and 'Count' do not match. Mismatch message: 'ObjectValuesDoNotMatch'

Example #3

The following example demonstrates how to create a custom object graph factory which determines which nodes in the object graph to compare.

For simplicity, we construct a factory that disregards any children.

 class SimpleObjectGraphFactory : ObjectGraphFactory
{
    public override GraphNode CreateObjectGraph(object o)
    {
        // Build the object graph with nodes that need to be compared.
        // in this particular case, we only pick up the object itself
        GraphNode node = new GraphNode();
        node.Name = "PersonObject";
        node.ObjectValue = (o as Person).Name;
        return node;
    }
}

Then, we utilize the factory to generate graph representations of the two object we want to compare.

 Person p1 = new Person("John");
p1.Children.Add(new Person("Peter"));

p1.Children.Add(new Person("Mary"));
Person p2 = new Person("John");

p2.Children.Add(new Person("Peter"));
ObjectGraphFactory factory = new SimpleObjectGraphFactory();

ObjectComparer comparer = new ObjectComparer(factory);

Console.WriteLine(
    "Objects p1 and p2 {0}",
    comparer.Compare(p1, p2) ? "match!" : "do NOT match!");

Execution of this code produces the following output…

 Objects p1 and p2 match!

… which is the expected result, given the fact that our simple factory completely disregards the Children of Person.

Conclusion

Comparing of objects is a common problem in automated test development and TestApi provides a simple generalized and extensible solution.