Unit Test MsBuild Custom Task
We have a few custom MsBuild tasks in our build system at work. MsBuild tasks are not as easy to test as you might hope. My previous effort involved creating a process to call MsBuild and swearing a lot. I've had another go and come up with a much better solution; creating an MsBuild engine in code. Check it out:
using System.IO;
using System.Text;
using Microsoft.Build.BuildEngine;
using Microsoft.Build.Framework;
using NUnit.Framework;
namespace MsBuildDemo
{
[TestFixture]
public class TestMsBuild
{
const string projectXml =
@"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
<Target Name='WriteToConsole'>
<Message Text='Hullo World'/>
</Target>
</Project>";
public bool CallMsBuild(string buildProject, out string consoleOutput)
{
var engine = new Engine{DefaultToolsVersion = "3.5"};
var project = new Project(engine);
project.LoadXml(buildProject);
var builder = new StringBuilder();
var writer = new StringWriter(builder);
WriteHandler handler = (x) => writer.WriteLine(x);
var logger = new ConsoleLogger(LoggerVerbosity.Normal, handler, null, null);
engine.RegisterLogger(logger);
bool result = engine.BuildProject(project);
engine.UnregisterAllLoggers();
consoleOutput = builder.ToString();
return result;
}
[Test]
public void CallMsBuildAndCaptureOutput()
{
string consoleOutput;
bool success = CallMsBuild(projectXml, out consoleOutput);
Assert.IsTrue(success, consoleOutput);
StringAssert.Contains("Hullo World", consoleOutput);
StringAssert.DoesNotContain("MSB4056", consoleOutput);
}
}
}
The bit I am particularly pleased with is subverting the WriteHandler for the console logger to capture the output as a string.
I'm using the 3.5 version of the engine and framework. When I first tried to call the Message task in the test I got an error "MSB4127: The "Message" task could not be instantiated from the assembly". The solution was to create the engine with the property DefaultToolsVersion = "3.5".
I also spent quite a bit of time getting a warning "MSB4056: The MSBuild engine must be called on a single-threaded-apartment." The apartment is to do with COM interop, and so probably doesn't matter in this case. It is however annoying. I was able to make NUnit run in STA mode by creating a config file for the test assembly. NUnit has an attribute to set the apartment mode but this didn't seem to work, possibly because I am calling the tests via the ReSharper test runner, or possibly due to incompetence.
So that's calling an MsBuild project from inside a test. You can use this technique to unit test custom MsBuild tasks.