Unit Testing Custom MSBuild Tasks

If you're developing custom MSBuild Tasks, and you're interested in testing them (and you should) using NUnit or VSTS there are a few considerations.

The first is the strategy you want to adopt for testing the custom task. In the spirit of “Unit” testing, you may choose to test only your code in the tightest scope possible. In this scenario, you may choose to new up your custom task, set some properties on it, and fire the Execute method.

Be forewarned however, that when you do this, you don't have the full Build engine around running around your task. If you use the built in task logging mechanism, i.e.

Log.LogMessage("QueueName {0}, Message {1}", this.queueName, this.message);

You'll get an exception thrown....

Error message:

Test method ExecuteTest threw exception: 
System.InvalidOperationException: Task attempted to log before it was initialized.

Stack trace:

at Microsoft.Build.Shared.ErrorUtilities.ThrowInvalidOperation(String resourceName, Object[] args)
at Microsoft.Build.Shared.ErrorUtilities.VerifyThrowInvalidOperation(Boolean condition, String resourceName, Object arg0)
at Microsoft.Build.Utilities.TaskLoggingHelper.LogMessage(MessageImportance importance, String message, Object[] messageArgs)
at Microsoft.Build.Utilities.TaskLoggingHelper.LogMessage(String message, Object[] messageArgs)

If you still want to test your custom task like this, you can test for the presencen of the build engine and not use it's built in logging features....i.e.

if (this.BuildEngine!=null)
     Log.LogMessage("QueueName {0}, Message {1}", this.queueName, this.message);

Of course, if you choose to fire off your custom build task within the scope of the build engine by executing a test build script harness (and that's a good idea too) you won't run into this problem, i.e.

Process buildProcess = new Process();
buildProcess.StartInfo.FileName = "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\MSBuild";
buildProcess.StartInfo.Arguments = "testscript.msbuild /target:testTarget”;
buildProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
buildProcess.StartInfo.CreateNoWindow = true;
//TODO: Assert that the script did what it was supposed to do.

My preference is to do both types of tests. The first style is helpful in the case of my actual custom task being faulty. If the first style fails, I'm very certain that the error is within the guts of that task. The second style is helpful in more of an integration test way. If the second style fails (and not the first) it means there is a problem in the way I'm calling my custom task within a build script.