Reputation: 3024
I have written a few MSBuild custom tasks that work well and are use in our CruiseControl.NET build process.
I am modifying one, and wish to unit test it by calling the Task's Execute() method.
However, if it encounters a line containing
Log.LogMessage("some message here");
it throws an InvalidOperationException:
Task attempted to log before it was initialized. Message was...
Any suggestions? (In the past I have mostly unit-tested Internal static methods on my custom tasks to avoid such problems.)
Upvotes: 29
Views: 7928
Reputation: 71
In assembly System.Web
in namespace
System.Web.Compilation
is a class MockEngine
which implements IBuildEngine
interface
in a way which describes Tim Murphy.
Upvotes: 0
Reputation: 41
I had the same problem. I solved it by stubbing the build engine. Like so (AppSettings is the MsBuild Task Name):
using Microsoft.Build.Framework;
using NUnit.Framework;
using Rhino.Mocks;
namespace NameSpace
{
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
MockRepository mock = new MockRepository();
IBuildEngine engine = mock.Stub<IBuildEngine>();
var appSettings = new AppSettings();
appSettings.BuildEngine = engine;
appSettings.Execute();
}
}
}
Upvotes: 4
Reputation: 4932
@Kiff comment on mock/stub IBuildEngine is a good idea. Here is my FakeBuildEngine. C# and VB.NET examples provided.
VB.NET
Imports System
Imports System.Collections.Generic
Imports Microsoft.Build.Framework
Public Class FakeBuildEngine
Implements IBuildEngine
// It's just a test helper so public fields is fine.
Public LogErrorEvents As New List(Of BuildErrorEventArgs)
Public LogMessageEvents As New List(Of BuildMessageEventArgs)
Public LogCustomEvents As New List(Of CustomBuildEventArgs)
Public LogWarningEvents As New List(Of BuildWarningEventArgs)
Public Function BuildProjectFile(
projectFileName As String,
targetNames() As String,
globalProperties As System.Collections.IDictionary,
targetOutputs As System.Collections.IDictionary) As Boolean
Implements IBuildEngine.BuildProjectFile
Throw New NotImplementedException
End Function
Public ReadOnly Property ColumnNumberOfTaskNode As Integer
Implements IBuildEngine.ColumnNumberOfTaskNode
Get
Return 0
End Get
End Property
Public ReadOnly Property ContinueOnError As Boolean
Implements IBuildEngine.ContinueOnError
Get
Throw New NotImplementedException
End Get
End Property
Public ReadOnly Property LineNumberOfTaskNode As Integer
Implements IBuildEngine.LineNumberOfTaskNode
Get
Return 0
End Get
End Property
Public Sub LogCustomEvent(e As CustomBuildEventArgs)
Implements IBuildEngine.LogCustomEvent
LogCustomEvents.Add(e)
End Sub
Public Sub LogErrorEvent(e As BuildErrorEventArgs)
Implements IBuildEngine.LogErrorEvent
LogErrorEvents.Add(e)
End Sub
Public Sub LogMessageEvent(e As BuildMessageEventArgs)
Implements IBuildEngine.LogMessageEvent
LogMessageEvents.Add(e)
End Sub
Public Sub LogWarningEvent(e As BuildWarningEventArgs)
Implements IBuildEngine.LogWarningEvent
LogWarningEvents.Add(e)
End Sub
Public ReadOnly Property ProjectFileOfTaskNode As String
Implements IBuildEngine.ProjectFileOfTaskNode
Get
Return "fake ProjectFileOfTaskNode"
End Get
End Property
End Class
C#
using System;
using System.Collections.Generic;
using Microsoft.Build.Framework;
public class FakeBuildEngine : IBuildEngine
{
// It's just a test helper so public fields is fine.
public List<BuildErrorEventArgs> LogErrorEvents = new List<BuildErrorEventArgs>();
public List<BuildMessageEventArgs> LogMessageEvents =
new List<BuildMessageEventArgs>();
public List<CustomBuildEventArgs> LogCustomEvents =
new List<CustomBuildEventArgs>();
public List<BuildWarningEventArgs> LogWarningEvents =
new List<BuildWarningEventArgs>();
public bool BuildProjectFile(
string projectFileName, string[] targetNames,
System.Collections.IDictionary globalProperties,
System.Collections.IDictionary targetOutputs)
{
throw new NotImplementedException();
}
public int ColumnNumberOfTaskNode
{
get { return 0; }
}
public bool ContinueOnError
{
get
{
throw new NotImplementedException();
}
}
public int LineNumberOfTaskNode
{
get { return 0; }
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
LogCustomEvents.Add(e);
}
public void LogErrorEvent(BuildErrorEventArgs e)
{
LogErrorEvents.Add(e);
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
LogMessageEvents.Add(e);
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
LogWarningEvents.Add(e);
}
public string ProjectFileOfTaskNode
{
get { return "fake ProjectFileOfTaskNode"; }
}
}
Upvotes: 11
Reputation: 691
You need to set the .BuildEngine property of the custom task you are calling.
You can set it to the same BuildEngine your current task is using to include the output seamlessly.
Task myCustomTask = new CustomTask();
myCustomTask.BuildEngine = this.BuildEngine;
myCustomTask.Execute();
Upvotes: 27
Reputation: 571
I have found that the log instance does not work unless the task is running inside msbuild, so I usually wrap my calls to Log, then check the value of BuildEngine to determin if I am running inside msbuild. As below.
Tim
private void LogFormat(string message, params object[] args)
{
if (this.BuildEngine != null)
{
this.Log.LogMessage(message, args);
}
else
{
Console.WriteLine(message, args);
}
}
Upvotes: 15
Reputation: 9254
If you have implemented the interface ITask you will have to initialise the Log class yourself.
Otherwise you should just inherit from Task in Microsoft.Build.Utilities.dll That implements ITask and does a lot of the leg work for you.
Here is the reference page for building a custom task, it explains quite a lot of it.
Building a custom MSBuild task reference
Also worth a look is
How to debug a custom MSBuild task
Other then that could you post the MSBuild XML you are using for calling your custom task. The code itself would obviously be the most help :-)
Upvotes: 6