Two Collection Extensions for Unit Testing

Last week, an associate asked me a question about unit testing and mock business objects. She was writing unit tests against some rather complicated business entities with a deep object tree, and wanted to know if it was possible for her to get access to one of the lower level objects in a collection inside a parent object? She only wanted to create one mocked parent-level entity.

So, at the same time, I was perusing the Microsoft Composite Application (Prism) source code, and saw the CollectionExtensions class. Hmm, I love extension methods. What if I could write an extension to a generic collection that would find an object based on a passed-in property of the particular object she was looking for? Think of it as a FindByID kind of functionality for collections.

So I quickly wrote this (I know. I should have written the Unit Test first):

    /// <summary>
    /// Find an object in a collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetItem<T>(this ICollection<T> collection,
                               Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        return null;
    }

And then I wrote a unit test to see how I did.

    [TestMethod]
    public void CanGetItemfromCollection()
    {
        var col = new Collection<string> {"Hello", "World"};
        var testItem = col.GetItem(c => c.Equals("World"));
        Assert.IsNotNull(testItem);
        Assert.AreEqual(testItem, "World");
    }

Nice. It passed. Now, I should extend this to support a more real-world example, say a person.

    /// <summary>
    /// Person class for simple mocking
    /// </summary>
    public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

So, I wrote another a unit test for the more complicated example:

    [TestMethod]
    public void CanGetItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
    {
        new Person {ID = 1, Name = "Bob"},
        new Person {ID = 1, Name = "Jim"}
    };
        var testItem = col.GetItem(c => c.ID.Equals(2));
        Assert.AreEqual(testItem.Name, "Jim");
    }

Oh, the evils of copy and paste (a true Anti-Pattern)! This test failed with a NullReferenceException in the GetItem extension method. Do you see why? How fortunate for me that I accidently left two objects with the same ID. For without that, there would be no blog post today. Still, I corrected the typo and ran a successful test.

I could certainly do an Assert.IsNotNull on the return value, but then if it was null, I couldn't proceed in the test without seeing the failed test notes detailing the NullReferenceException in the called method. It did not break the running test; it only failed the test, and that's not really a bad thing at all, considering that a typo caused it.

If I threw a NullReferenceException myself in the extension method, that could be safer/saner for the developer depending on your point of view. That version as shown below still failed the test, but did not break it.

    /// <summary>
    /// Find an object in a collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetItem<T>(this ICollection<T> collection, Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        throw new NullReferenceException("Collection object of type " + typeof(T) + 
                " not found for predicate provided and therefore, not initialized");
    }

Here's an edited version of what showed up in the Test Results:

Test method CanGetItemfromCollectionByProperty threw exception: System.NullReferenceException: Collection object of type CollectionExtensionsFixture+Person not found for predicate provided, and therefore, is not initialized.

So, a typo could break an app using the extension method at runtime versus the more desired compile time break. So, I tried to just return default(T) in the extension method instead of throwing an exception, and add Assert.IsNotNull(testItem) in the test method. I could do this safely and get a clean failure with or without the typo:

It would be good to figure out a way to new up a class<T> in the extension method if one isn't found with the provided predicate.

So, I walked down the hall to my neighborhood C# guru, and asked about the issue with the return value in my extension method. He quickly pointed out that I might need to consider writing specialized methods for value and reference types, and off I went to seek extension method nirvana.

At first, I tried to just add a where T : class to the basic GetItem extension method, but that made no difference; I was still getting a null reference back. I could fudge the Unit Test with an Assert.IsNotNull and then some if testing, but that's not the right way to go about it. Here's the 'abomination':

    [TestMethod]
    public void CanGetItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
        {
            new Person {ID = 1, Name = "Bob"},
            new Person {ID = 2, Name = "Jim"}
        };
        var testItem = col.GetItem(c => c.ID.Equals(2));
        Assert.IsNotNull(testItem);
        if (testItem != null)
            Assert.AreEqual(testItem.Name, "Jim");
    }

Code smell, right? So I tried where T : new() in the extension method, but quickly got a compiler error, because in my basic single string test method, the extension method cannot handle the fact that the string object has no empty constructor. So, it looks like my guru was correct. Here's the answer:

    /// <summary>
    /// Find a value type object in a value-type collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetValueItem<T>(this ICollection<T> collection, Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        return default(T);
    }

    /// <summary>
    /// Find a reference type object in a collection of reference types.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetReferenceItem<T>(this ICollection<T> collection, Predicate<T> isMatch) 
where T : class, new() { foreach (var item in collection) { if (isMatch(item)) return item; } return new T(); }

For a value type, we simply return the default value of a new object (e.g., String.Empty, etc.). For a reference type, we simply return a new empty object. As long as there are no constraints preventing an empty new object, we'll be fine.

Testing now becomes straight-forward and gracefully supports typo's (and resulting nulls or empty objects) in either value type or reference type scenarios. Here are the revised test methods:

    [TestMethod]
    public void CanGetValueItemfromCollection()
    {
        var col = new Collection<string> { "Hello", "World" };
        var testItem = col.GetValueItem(c => c.Equals("World"));
        Assert.AreEqual(testItem, "World");
    }

    [TestMethod]
    public void CanGetReferenceItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
        {
            new Person {ID = 1, Name = "Bob"},
            new Person {ID = 2, Name = "Jim"}
        };
        // known good by ID
        var testItem = col.GetReferenceItem(c => c.ID.Equals(2));
        Assert.AreEqual(testItem.Name, "Jim");
        // known does not exist
        testItem = col.GetReferenceItem(c => c.ID.Equals(3));
        Assert.IsNull(testItem.Name);
        // known good by Name
        testItem = col.GetReferenceItem(c => c.Name.Equals("Bob"));
        Assert.AreEqual(testItem.ID, 1);
    }

    /// <summary>
    /// Person class for simple mocking
    /// </summary>
    public class Person
    {
        /// <summary>
        /// ID
        /// </summary>
        public int ID { get; set; }
        /// <summary>
        /// Name
        /// </summary>
        public string Name { get; set; }
    }

Of course, your Person class comes from a mocked service or other separate class, right? Now if only I could mooch some of those Christmas cookies from my associate. And I hope your Christmas is joyful, and wish everyone a 2010 that is pronounced correctly.

Comments? Questions? Flames? Send me an email by clicking on my name below.

Bob Baker

posted @ Monday, December 21, 2009 11:32 PM

Print

 

Comments have been closed on this topic.