private override

3Sep/095

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 );
}
 

view raw This Gist brought to you by GitHub.

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 );
}
 

view raw This Gist brought to you by GitHub.

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 );
}
 

view raw This Gist brought to you by GitHub.

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++;
    }
}

view raw This Gist brought to you by GitHub.

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.

Filed under: Uncategorized 5 Comments
23Aug/094

connect windows mobile 6 Emulator to ActiveSync

20Apr/091

7 tips for solid client interactions

9Apr/090

indy code camp

30Mar/090

2 amazazing productivity tools

12Mar/091

Dependency Injection and Service Location

25Feb/091

Making the ‘using’ statement, more usable

23Feb/090

build automation evolution – CruiseControl.rb and .NET (and TFS!)

23Feb/094

Re: How do you stay AND grow? A commentary

23Feb/095

build automation evolution – Rake and .NET