I have run into the unenviable task of maintaining someone else’s stale, and poorly architected codebase. One of my self-directed initiatives was to begin to implement some unit testing, especially in the most volatile areas of the application to begin to automate some of the sorely lacking regression testing as new features are added and defects are resolved. This is the last I’ll coment on the state of the current codebase and my incredulity that modern software applications are still build hand-to-keyboard first without thinking about design and architecture.
As I began to work with the existing code base, I ran into a situation where there were a handful of objects implement the old IEnumerable/IEnumerater (correct, not the generic kind). I began to feverishly attempt to unit test these but ran into all sorts of what I can only explain as “language semantic” errors. Granted it has been several years since I had to go back and look at the IEnumerable pattern to understand again how it works instead of just inheriting from List<>. For anyone else in my shoes, I’ll explain quickly and then get into the testing.
When the exercising code begins to enumerate over an IEnumerable, such as a foreach loop:
A couple different things happen. First, the loop calls “GetEnumerator()” from the IEnumerable object, in this case ProjectCollection, which returns an IEnumerator. IEnumerator implements a few different methods and properties, most notably:
MoveNext serves only to increment an indexer into the underlying array of objects with which the IEnumerator is working. If the incement is successful, i.e., it does not exceed the length of the underlying collection, we return true, otherwise false. As the exercising code iterates over the collection, it knows to break the loop once MoveNext returns false.
Current, then uses this indexer to return the current item in the underlying collection based on the value of the iterator. So a couple things we need to do in order to implement the pattern correctly. First, it is important to know the pattern assumes the initial value of the internal iterator is always less than the first value in the collection…usually -1.
Secondly, we should take care to do appropriate error handling since we are providing the iterating functionality ourselves…but with proper testing we will get there.
Start Reading Here
We will start working with the following series of classes:
Additionally, we have a worker class which we will use to exercise the enumeration with the following method:
ProductList GetProductDataList(string lanId)
And ProductList : List<Product>
OK, enough setup, let’s jump in. Testing the Enumerator and IEnumerable are pretty straight forward, and I won’t spend a lot of time there. You can look at the attached source code. The more difficult thing is to mock out and stub the behavior of these classes, especially since the ProductCollection uses a method called Load to hydrate itself from some data source such as a service or database.
The first step here is to dependency inject the ProjectCollection object into the Worker object…We can talk about IoC Containers and the like, but that is not the purpose of this blog so we will use a simple constructor injection pattern:
Now from our Test class we can use RhinoAutoMocker to setup the worker class and setup the Ctor DI of the IProjectCollection:
Next we will generate a mock IEnumerator to be returned by the GetEnumerator method of the IEnumerable object:
Now the fun starts, we need to start stubbing out the behavior of the interface:
Interestingly, here is where the problems start for me. I had originally stubbed out the MoveNext method to always return true without a limit. This causes the iterator in the exercising class to always return true and never break the loop. Which is why I need to put the .Repeat.Once() constraint on the call.
Similarly, the Current property will always return the same object if we don’t constrain it to only repeat once.
Once we have that all working, then we can set up the IProductCollection.GetEnumerator to return the productEnumerator we just mocked out:
And then call the underlying method on the exercising code and our assertions:
There you have it.