Mocking out calls in unit tests to a TFS Server using Typemock
If you are developing custom application using the TFS API then there is a good chance you will want to mock out the calls to your TFS server to enable better testing of the business logic in your application. The architecture of the TFS API does not lend itself to mocking using the standard means provided in most auto-mocking frameworks i.e there is not an interface for all the objects you care about. However, with Typemock Isolator you can fake the classes required, as Isolator can fake an instance of virtually any class.
So say we wanted to write a simple build monitor application for TFS Team Build system, we need to connect to a TFS server, get a list of historic builds, then select the last successful one. So our business logic method is as follows
1/// <summary>
/// Gets the last successful build
1/// </summary>
public static IBuildDetail GetLastNonFailingBuildDetails(string url, string projectName, string buildName)
1{
using (TeamFoundationServer tfs = new TeamFoundationServer(url))
1 {
IBuildServer buildServer = (IBuildServer)tfs.GetService(typeof(IBuildServer));
1 return buildServer.QueryBuilds(projectName, buildName).Last(b => b.Status == BuildStatus.Succeeded || b.Status == BuildStatus.PartiallySucceeded);
}
1}
2```
3
4To test this, you would usually need a TFS server, with a set of historic build data already on it, but with Typemock you can avoid this requirement. OK we have to write bit of supporting code, but most of it would be common to a suite of tests, so the effort will not be too high overall and by doing it you get a test that can be run as part of the build process.
5
6To be able to unit test our business logic (the last line of code in reality in this sample) we need to mock the call to the TeamFoundationServer (which is usually the blocking point for most mocking frameworks) and then mock the call to get the IBuildServer and return a set of data (which is usually possible with mocking frameworks).
7
8Using Typemock we can get around these problems, the comments for each step are inline with the code.
9
10```
11\[TestClass\]
public class TFSTests
1{
\[TestMethod\]
1 public void The\_last\_completed\_and\_non\_failed\_build\_can\_be\_found()
{
1 // Arrange
// Create the fake TFS server
1 var fakeTfsServer = Isolate.Fake.Instance<TeamFoundationServer>();
// Swap it in the next time the constructor is run
1 Isolate.Swap.NextInstance<TeamFoundationServer>().With(fakeTfsServer);
2```
3
4```
5 // Create a fake build server instance
var fakeBuildServer = Isolate.Fake.Instance<IBuildServer>();
1 // Set the behaviour on the TFS server to return the build server
Isolate.WhenCalled(() => fakeTfsServer.GetService(typeof(IBuildServer))).WillReturn(fakeBuildServer);
// Create some test data for the build server to return
1 var fakeBuildDetails = CreateResultSet(new List<BuildTestData>() {
new BuildTestData() {BuildName ="Build1", BuildStatus = BuildStatus.Failed},
1 new BuildTestData() {BuildName ="Build2", BuildStatus = BuildStatus.PartiallySucceeded},
new BuildTestData() {BuildName ="Build3", BuildStatus = BuildStatus.Failed},
1 new BuildTestData() {BuildName ="Build4", BuildStatus = BuildStatus.Succeeded},
new BuildTestData() {BuildName ="Build5", BuildStatus = BuildStatus.PartiallySucceeded},
1 new BuildTestData() {BuildName ="Build6", BuildStatus = BuildStatus.Failed}
});
1 // Set the behaviour on the build server to return the test data, the nulls mean we don’t care about parameters passed
Isolate.WhenCalled(() => fakeBuildServer.QueryBuilds(null, null)).WillReturn(fakeBuildDetails);
// Act
1 // Call the method we want to test, as we are using a fake server the parameters are actually ignored
var actual = TFSMocking.BuildDetails.GetLastNonFailingBuildDetails("http://FakeURL:8080/tfs", "FakeTeamProject", "FakeBuildName");
// Assert
1 Assert.AreEqual("Build5", actual.BuildNumber);
}
/// <summary>
1 /// A helper method to hide the Typemock code used to create each build results set
/// </summary>
1 /// <param name="builds">The parameters to populate into the build results</param>
/// <returns>A set of build results</returns>
1 private IBuildDetail\[\] CreateResultSet(List<BuildTestData> builds)
{
1 var fakeBuilds = new List<IBuildDetail>();
foreach (var build in builds)
1 {
// Create a fake build result instance
1 var fakeBuildDetails = Isolate.Fake.Instance<IBuildDetail>();
// Set the properties, in this sample we only set a couple of properties, but this can be extended
1 fakeBuildDetails.BuildNumber = build.BuildName;
fakeBuildDetails.Status = build.BuildStatus;
1 fakeBuilds.Add(fakeBuildDetails);
}
1 return fakeBuilds.ToArray();
}
1}
2```
3
4```
5/// <summary>
/// A holding class for the build data we are interested in faking
1/// </summary>
public class BuildTestData
1{
public string BuildName {get;set;}
1 public BuildStatus BuildStatus {get;set;}
}
1
2This sample is obviously fairly simple, but not that unrealistic. I have certainly written simple logic like this for build status applications. You could of course use some different architecture to make the business logic a bit more testable, but for such as basic requirement it is very tempting to keep it simple.
3
4What I hope this post shows is that there is a way to test this type of logic without the need for a TFS server that has a suitable set of pre-created data and that the basic technique can be extended as much as is required to provide a mocked framework to allow unit testing of more complicated business logic.