Testing Events with Awesomeness
How many of you have written this code when testing an event?
[Test]
public void LameTest()
{
var objectWithEvent = new ClassWithEvent();
var awesomeFired = false;
objectWithEvent.AwesomeEvent += ( o, e ) => awesomeFired = true;
objectWithEvent.DoSomethingAwesome();
Assert.IsTrue( awesomeFired );
}
or its big brother:
[Test]
public void LessLameTest()
{
var objectWithEvent = new ClassWithEvent();
var awesomeCount = 0;
objectWithEvent.AwesomeEvent += ( o, e ) => awesomeCount++;
objectWithEvent.DoSomethingAwesome();
Assert.AreEqual( 1, awesomeCount );
}
Every time I write that code, it feels WET (read: not DRY), especially when I’m doing it for more than one event in the same test.
What I’d like to see is something like:
[Test]
public void AwesomeTest()
{
var objectWithEvent = new ClassWithEvent();
var awesomeTracker = new EventTracker<EventHandler>();
objectWithEvent.AwesomeEvent += awesomeTracker.Delegate;
objectWithEvent.DoSomethingAwesome();
Assert.IsTrue( awesomeTracker.WasCalled );
}
Yeah! Now that’s what I’m looking for. Is this possible?
Yup.
How? Let’s see.
So basically what this means, is that we have to generate a method (at runtime) that matches the signature of the delegate we pass to the tracker as the type argument (so it can be added as a handler for the event). Inside the method, we need to update our tracking variable (either setting a flag, or incrementing a counter). We can ignore any of the arguments passed to the delegate (e.g. the sender, event args, or anything else), since for our purpose we really don’t care about them. What I think is so cool about this is that it works for ANY type of delegate you pass to it! Sort of like generics, but for methods (makes sense in a meta sort of way).
Here is the implementation:
public class EventTracker<TDelegate>
where TDelegate : class
{
public static implicit operator TDelegate( EventTracker<TDelegate> tracker )
{
return tracker.Delegate;
}
public EventTracker()
{
var delegateMeta = typeof( TDelegate ).GetMethod( "Invoke" );
var delegateParams = delegateMeta
.GetParameters()
.Map( param => param.ParameterType )
.Prepend( GetType() )
.ToArray<Type>();
var invokedMethod = GetType().GetMethod( "Invoked", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public );
var dynamicHandler = new DynamicMethod( "", delegateMeta.ReturnType, delegateParams, GetType(), true );
var generator = dynamicHandler.GetILGenerator();
generator.Emit( OpCodes.Ldarg_0 );
generator.Emit( OpCodes.Call, invokedMethod );
generator.Emit( OpCodes.Ret );
Delegate = dynamicHandler.CreateDelegate( typeof( TDelegate ), this ) as TDelegate;
}
public TDelegate Delegate
{
get;
private set;
}
public int CallCount
{
get;
private set;
}
public bool WasCalled
{
get { return CallCount > 0; }
}
public bool WasNotCalled
{
get { return !WasCalled; }
}
// called via the dynamic method
private void Invoked()
{
CallCount++;
}
}
This is my first foray into IL generation… not exactly my forte. A little more on IL generation next time. Oh yeah, must give credit where it is due… thanks to Shawn for the help when my fu was weak.
