Using the Fakes Framework to Test TFS API Code (Part 2 of 2)

TFSAPI
3978

In Part 1, we started faking some TFS objects. We got as far as faking the TeamProjectCollection and WorkItemStore. In this post, we’ll complete the test for copying work items by providing a fake QueryHierarchy and a fake list of WorkItems.

Binding IEnumerables

Since the QueryHierarchy is an IEnumerable, we’ll need to either fake the GetEnumerator() method, or find a way of making the fake QueryHierarchy enumerable! The problem with trying to fake the GetEnumerator() method is that you end up in an infinite loop – if you’re trying to get the enumerator for sub nodes of the hierarchy, you’ll also need to call GetEnumerator() which will call GetEnumerator() and so on and so on… so we’ll see if we can make the fake QueryHiearchy behave like an enumerable object. Which is really quite easy when you know how!

We create a list of QueryFolders (outside the ShimsContext, since we want these object to be real and not fake) and then call the Bind() method on the fake QueryHierarchy:

var myQueries = new QueryFolder("My Queries");
var sharedQueries = new QueryFolder("Team Queries");
sharedQueries.Add(new QueryDefinition("My Tasks", "SELECT System.Id FROM WorkItems WHERE System.WorkItemType = 'Task'"));
sharedQueries.Add(new QueryDefinition("My Bugs", "SELECT System.Id FROM WorkItems WHERE System.WorkItemType = 'Bug'"));
var topQueries = new List() { myQueries, sharedQueries };

using (ShimsContext.Create())
{
// set up the fakes
var fakeHierarchy = new ShimQueryHierarchy();
fakeHierarchy.Bind(topQueries);

Now when we run the code, the QueryHierarchy behaves like an enumeration and a call to find the “My Tasks” query will result in a QueryDefinition being returned!


Faking an Interface


The next thing we’ll need to fake is the RunQuery() method on the Query. The first snag we’ll hit is that the RunQuery() method in the WorkItemCopyer uses the IGroupSecurityService to resolve the current user’s display name for any @me macros in the query. So we’ll first need to fake that. Hang on – how do you fake an interface?? Enter Stubs.


So far we’ve only used Shims. For faking interfaces, we’ll need to switch to Stubs. The only method on the IGroupSecurityService interface that we want to fake is the ReadIdentity() method. So we can fake that and return a fake Identity object. Again, we’ll run into the problem of wanting a real object in a fake world (i.e. the ShimsContext) so we’ll use a sneaky way of working around that. Here’s the code for the fake IGroupSecurityService:

var fakeSecurityService = new StubIGroupSecurityService()
{
ReadIdentitySearchFactorStringQueryMembership = (searchFactor, criteria, identity) => ShimsContext.ExecuteWithoutShims(() => new Identity() { DisplayName = "Bob" })
};
ShimTfsConnection.AllInstances.GetServiceOf1((t) => fakeSecurityService);

This will create the Identity object “outside” the current ShimsContext. Of course, we need to tell the TfsConnection GetService call to return the fake security service when asked for an IGroupSecurityService (which is done in the last line above).


Faking Constructors


The next thing that the WorkItemCopyer code does is instantiate a Query object in order to execute the Work Item Query. We’ll need to fake the constructor in order to return a fake Query. The method we particularly want to fake out on the Query object is the RunQuery() method, which is going to return a WorkItemCollection. We’ll want to create a list of Work Items and bind a fake WorkItemCollection to the list so that we can enumerate the fake results. There is also a call to the IterationPath setter and a couple of getters on the WorkItem itself, so we’ll fake that at the same time. Here’s the code:

ShimQuery.ConstructorWorkItemStoreStringIDictionary = (q, store, text, dict) =>
{
new ShimQuery(q)
{
RunQuery = () =>
{
var workItemType = new ShimWorkItemType()
{
NameGet = () => "Task"
};
var list = new List()
{
new ShimWorkItem()
{
IdGet = () => 12,
TitleGet = () => "Some Work Item",
TypeGet = () => workItemType,
IterationPathSetString = (path) => { },
}
};
var workItems = new ShimWorkItemCollection()
{
CountGet = () => list.Count
};
workItems.Bind(list);
return workItems;
}
};
};

Finally, we’ll create a couple of counters to make sure that our code calls the copy and the save methods of the work items for each work item.

var copyCount = 0;
ShimWorkItem.AllInstances.Copy = (w) =>
{
copyCount++;
return new ShimWorkItem(w);
};
var saveCount = 0;
ShimWorkItem.AllInstances.Save = (_) => saveCount++;

// test instantiate
var target = new WorkItemCopyer(fakeTPC, "Code11");
Assert.IsNotNull(target);

// test find query
var query = target.FindQuery("My Tasks");
Assert.IsNotNull(query);

// test run query
target.RunQuery(query);
Assert.AreEqual(1, target.WorkItems.Count);

// test copy work items
var count = target.CopyWorkItems("Test Iteration");
Assert.AreEqual(1, count);
Assert.AreEqual(1, copyCount);
Assert.AreEqual(1, saveCount);

Voila! We’ve been able to test our code without actually connecting to a real TFS service. And coverage? Well, it’s up to 97% for the WorkItemCopyer class. Not too bad!


The completed solution is available on my skydrive.


(More) Happy Faking!