Practical .NET

Checking Collections and Working with Objects in Visual Studio Test

Peter looks for help in building an extension method that will let him compare two objects in a Visual Studio Test. In return, he introduces the CollectionAssert class.

I admit it: I need help. I've got a useful little utility that helps me with a problem, but it doesn't quite do everything I want. If someone out there has an idea, I'm open to suggestions. I'll explain my problem and, along the way, introduce a feature of Visual Studio Test that you might not know about: CollectionAssert.

If you're doing Test-Driven Development (TDD) and need to check a collection, the CollectionAssert class should be your best friend. Methods on the CollectionAssert class let you check to see if two collections are the same or one contains another (AreEquivalent, IsSubsetOf), for instance. There are also methods to determine if a collection contains or omits some item (Contains, DoesNotContain). That's all well and good as long as your collection contains values: If your collection contains objects (references), these methods won't work.

In fact, this isn't just a problem with TDD and collections; it's a problem with TTD and any kind of object. This code, for instance, is obviously comparing two Customers that I would consider "the same":

Dim cust1 As Customer
Dim cust2 As Customer
cust1 = CustomerFactory.GetCustomerById("A123")
cust2 = CustomerFactory.GetCustomerById("A123")
Assert.AreEqual(cust1, cust2)

But the Assert.AreEqual will report that the two objects are different (a caveat: you can avoid the problem by implementing the IEquatable interface). The reason that AreEqual says that the two objects are different is, of course, because they are: I'm comparing two different objects that happen to represent the same business entity. This is a problem for me, because I often want to retrieve a Customer object from my "code under test," then retrieve a Customer object for same customer from some other source and then, to finish my test, compare the two objects. Sadly, that test will always fail because they are two different objects (I've got a too-long discussion on the topic if you're interested).

In many cases, what I mean by the two objects being "the same" is that they have the same values in their public properties. I don't want to have to implement IEquatable on every class I create, and besides, I may want to test with classes with source code I can't change. I could rewrite my tests by comparing every individual property, but I don't want to. Not only would this be tedious code to write, it's open to error because I might miss a property comparison. It's also brittle code: If I add a property to the class, it's unlikely I'll remember to go back and update every test; and as a result, those tests will no longer be checking every property on an object.

First Cut at a Solution
But I can use CollectionAssert to provide a partial solution to this problem. First, I create an extension method that returns a collection holding string versions of all the property values in an object, sorted by the property name:

public static class PHVExtensions
{
  public static List<string> GetPropertyValues(this object itm)
  {
   List<string> propsList = (from i in itm.GetType().GetProperties()
                             orderby i.Name
                             select i.GetValue(itm) == 
                               null ? "<nothing>" : i.GetValue(itm).ToString()).
                             ToList();
   return propsList;
  }
}

Now, I can compare my two Customer objects like this:

CollectionAssert.AreEqivalent(cust1.GetPropertyValues(), cust2.GetPropertyValues())

This isn't a perfect solution. First, if one object has a property set to Nothing/null and the other object has the same property set to the string "<nothing>" this code is going to say that the two properties are identical. That seems sufficiently unlikely that I'm not going to worry about it.

More importantly, any property that holds an object (a reference) isn't going to test well: The ToString() method on those properties' values will probably just return the name of the class. I could just test the properties holding objects separately from the rest of my properties, but that seems open to the same problems I mentioned before: I'll miss testing a property or I'll add a new reference property to the class and forget to update my code. I'd rather have something automated.

But I've already written my solution: I can apply my GetPropertyValues to the "object properties" by turning my method into a recursive method. I don't know how to do recursion in LINQ (I suspect I could do something interesting with lambda expressions) and, even if I could figure that out, I'd probably end up with a "collection of collections" rather than a simple list of property values. My first step, therefore, is to rewrite my extension method to use a foreach loop. As long as I'm doing that, I'll stop retrieving every property and just retrieve the ones I'm interested in for my "same business entity" tests: public properties tied to an instance of the class. With those changes, the main part of my loop looks like this:

public static List<string> GetPropertyValues(this object itm)
{
  List<string> propsList = new List<string>();
  IEnumerable<PropertyInfo> props = itm.GetType().
         GetProperties(BindingFlags.Public | BindingFlags.Instance).
         OrderBy(p => p.Name);
  foreach (PropertyInfo i in props)
  {
    Object val = i.GetValue(itm);
    if (val == null)
    {
      propsList.Add("<nothing>");
    }
    else 
    {
      propsList.Add(val.ToString());
    }
  }
  return propsList;
}

Now, I just need to check to see if a property's value is a reference type, and if it is, call my GetPropertyValues method on that property. I can add the returned values from that recursive call onto the end of the list of property values that I'm building. The IsValueType will tell me if an item is a value type, so I can use that to spot my reference properties. The inside of my loop now looks like this:

Object val = i.GetValue(itm);
if (val == null)
{
   propsList.Add("<nothing>");
}
else if (i.PropertyType.IsValueType)
{
  propsList.Add(val.ToString());
}
else
{
  propsList.AddRange(val.GetPropertyValues());
}

And this works…mostly. I can't figure out how to handle properties that are collections. Presumably, if I had some reliable way to iterate through the collection, I could call my GetPropertyValues method on each object in the collection. But I can't figure out a way to iterate through the collection.

Any ideas?

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube