Testing

Beyond the Event Horizon: Events You Don’t Own

It’s easy to retrieve an invocation list when you have a delegate instance in hand.  When it comes to events, its easy to get your hands on the backing delegate instance when you own the class wherePA310020 the event is declared.

However, there is a whole universe of events that you don’t own.   These events are declared on BCL classes, third party components, or on that one God class that your boss won’t let you change.  Or maybe you’re picky about encapsulation and want to get your hand on the delegate without adapting your production code to the needs of your tests.  If you find yourself in any of these situations, .NET reflection provides everything you need to get your hands on the delegate and write tests.  This article will show you how to extract the delegates backing POCO events, and in later posts I’ll cover more difficult scenarios—including the WinForms event system.

“What’s a POCO event?” you ask.  POCO events are just my term for the simplest possible event you could write.  Find out more about what the compiler does when you declare an event by visiting the previous posts in this article.  In Part One: “Beyond the Event Horizon: Delegate Basics” I introduced some relevant aspects of .NET delegates.  In part Two: “Beyond the Event Horizon: Event Basics” I defined POCO events and covered their implementation.

In a roundabout way, this series of posts is about ApprovalTests.  If you haven’t heard of ApprovalTests, it’s a great open source library which you can use to enhance your tests.  I gave a 10,000 foot overview of ApprovalTests in part one of this series, but if you want to get the most out of reading this article, check out Llewellyn Falco’s series on YouTube.

If you are really interested in the nitty-gritty and playing around with the ideas in this article, check out the accompanying  code repository on GitHub.  Also remember that the codebase I’m building in these articles is  a reimplementation of features already available in ApprovalTests 2.0.  If you want to use these features, don’t waste your time with cut-and-paste, go grab ApprovalTests.

With all that intro out of the way, lets talk about reflection.

Testing Events You Don’t Own

In the previous article, I created a class called Poco. This simple class declares an event that signals when Poco completes it’s (imaginary) work.  As the example progressed, I started to make some assumptions about Poco’s behavior and add restrictions to what I could change about Poco’s implementation.  For this example, I’ll keep all the previous assumptions:

  • Poco is part of a primitive layer, meant to be accessed through a higher-level API.
  • Poco’s main activity is performed by DoWork, which is too expensive/inconvenient to call.
  • PocoClient is part of the high-level API and must listen to the Poco.ProcessCompleted event.
  • I don’t control Poco, so simply changing it’s implementation is not an option.

For a simple class like Poco it’s not hard to retrieve the handler using reflection.  First I’ll get rid of the GetProcessCompletedHandler method I added to Poco in the previous example. I’ll implement it as an extension method instead and make no changes to the test.  The extension method isn’t strictly necessary but it will keep the reflection code from cluttering up the test.

I’ll create a static class to host the extension method.

using System;
using System.Linq;
using System.Reflection;

public static class ReflectionUtility
{
    public const BindingFlags NonPublicInstance = BindingFlags.Instance | BindingFlags.NonPublic;

    public static EventHandler GetProcessCompletedHandler(this Poco poco)
    {
        var matchingFields = from fieldInfo in poco.GetType().GetFields(NonPublicInstance)
                             where typeof(EventHandler).IsAssignableFrom(fieldInfo.FieldType)
                             && fieldInfo.Name == "ProcessCompleted"
                             select (EventHandler)fieldInfo.GetValue(poco);
        return matchingFields.Single();
    }
}

This extension method allows the GetPocoEventInvocationList test to pass without altering Poco.  I’ll add a test for PocoClient now, which should verify the scenario that got us here, then spend some time examining how GetProcessCompletedHandler works.

[TestMethod]
public void PocoClientListensToPoco()
{
    Poco poco = new Poco();
    PocoClient client = new PocoClient(poco);
    DelegateUtility.VerifyInvocationList(poco.GetProcessCompletedHandler());
}

The results confirm that PocoClient is doing its job:

[0] = Void LogCompletionTime(System.Object, System.EventArgs)

Reflection Anatomy

Let’s get into GetProcessCompletedHandler guts.  My goal is to get my hands on the event’s backing field.  I know from using Reflector that the field is a private instance field, and although it is invisible, I know the name the compiler will choose for the field—it’s the same name I gave to the simple event declaration.

I can use reflection to get any information I want off the type, as long as I know how its declared and what its name is.  By default, reflection methods supply public instance members.  If you want to include the private members, you have to be specific about the BindingFlags you pass in.  So I create a const in ReflectionUtility to specify the non-public (i.e., private, protected, internal) and instance flags.  My primary reason for giving these flags a name is to increase readability, but as ReflectionUtility grows, this const will also save me a lot of typing.

public const BindingFlags NonPublicInstance = BindingFlags.Instance | BindingFlags.NonPublic;

With that out of the way, I can request information about the poco instance’s private fields.  First, I need to get a Type object which corresponds to Poco (the class) which I can obtain by calling GetType on poco (the object).  The Type object describes the Poco class, and I use GetFields with my binding flags to get the collection of descriptors for the Type’s private fields.

from fieldInfo in poco.GetType().GetFields(NonPublicInstance)

Next, I filter for EventHandlers:

where typeof(EventHandler).IsAssignableFrom(fieldInfo.FieldType)

Then I filter by name:

&& fieldInfo.Name == "ProcessCompleted"

FieldInfo instances describe fields, but I want the actual field value from the poco instance.  To retrieve the actual instance value, I use the GetValue method, which returns an object reference to the actual value from poco.  I don’t want an object, I want an EventHandler.  I can safely make that cast because my first filter ensures that this object is castable to EventHandler.  This is what I end up selecting:

select (EventHandler)fieldInfo.GetValue(poco);

Because there can only be one field named “ProcessCompleted”, the name filter ensures that there is only one member in the collection returned by this query.  So, I pull that member out of the collection using Single:

return matchingFields.Single();

And that, it would seem, is all you need to know about retrieving the delegate field backing an event..

Making it Better

The best part about having green tests is that you can stop thinking so hard and just play.  GetProcessCompletedHandler is OK, but it can be improved.  I’ll start by fixing the most obvious defect, the method doesn’t check its input.  What happens when I throw a null in there?

[TestMethod]
public void NullHasNoProcessCompletedHandler()
{
    Assert.IsNull(ReflectionUtility.GetProcessCompletedHandler(null));
}

Not surprisingly, this test throws a NullReferenceException before even reaching the assertion.  The exception occurs when the query calls GetType on the null value.   This check should fix the problem for now.

public static EventHandler GetProcessCompletedHandler(this Poco poco)
{
    if (poco == null)
    {
        return null;
    }

Now that the most obvious problem is out of the way, my biggest complaint is the magic string “ProcessCompleted”.  If events were (even) more like properties, I could use an expression tree to specify the member with a lambda.  Alas events are not properties, and they don’t work well (at all?) with expression trees.  Perhaps I can make the magic string palatable by promoting it to a parameter.

public static EventHandler GetProcessCompletedHandler(this Poco poco, string eventName)
{
    if (poco == null)
    {
        return null;
    }

    var matchingFields = from fieldInfo in poco.GetType().GetFields(NonPublicInstance)
                         where typeof(EventHandler).IsAssignableFrom(fieldInfo.FieldType)
                         && fieldInfo.Name == eventName
                         select (EventHandler)fieldInfo.GetValue(poco);
    return matchingFields.Single();
}

I used my refactoring tool to introduce the new parameter, and it automatically updated the test call sites to look something like this:

[TestMethod]
public void PocoClientListensToPoco()
{
    Poco poco = new Poco();
    PocoClient client = new PocoClient(poco);
    DelegateUtility.VerifyInvocationList(poco.GetProcessCompletedHandler("ProcessCompleted"));
}

That seems like a small improvement but I’m not really satisfied with it.  I’ll file away the magic string for a moment, because now I notice something else about the extension method.  The only Poco-specific piece of data was the event name, and now that I have moved the magic string, this method should now work as an extension on object.  After making that change I also rename the parameter to “value”, since the instance is not necessarily a Poco anymore.

public static EventHandler GetProcessCompletedHandler(this object value, string eventName)

My test still pass, so this change is all good.  Now I notice that the method name still has “ProcessCompleted” in it.  That name no longer reflects what the method does, so I rename to “GetEventHandler'” with my refactoring tool.

public static EventHandler GetEventHandler(this object value, string eventName)

My test still pass.  Now I’m wondering if I can make it even more generic.  I’m still not happy with my magic string either.  Instead of one magic string in one extension method, I now have a magic string in every test.  So, promoting the string to a parameter made the extension method look nicer, but at the expense of making everything else uglier.

The other problem with this method is that I can only retrieve the delegate for one event at a time.  Poco is simple, it has one event handler.  PocoClient has a simple relationship with Poco. What if both objects were more complex? Poco might have many handlers, and PocoClient might subscribe to some subset of those handlers. Querying event handlers 1 by 1 would be tedious in a scenario like that.  I would need to know their names (and spell them correctly), and each would require its own test. If I introduced new events to Poco, I’d have to remember to add a test each time.  Chances are good that sometime down the line an event will be missed, and end up uncovered.

I can eliminate both maintenance problems in one (big) step, just by getting rid of the name filter.  Instead of retrieving just one event delegate, I could get the inventory of all the EventHandler delegates on Poco.  When Poco gets new events, they will show up in the test as soon as they’re introduced, without writing any new test code.  Instead of using the Name property as a filter, I could include it as part of the return value.  However, without the filter on the field name, I can’t use Single to pull the result from the collection, because there might be more than one field with handlers attached.  So I’ll have to change the return type as well.  Here is what my method looks like after making these changes.

public static IEnumerable<Tuple<string, EventHandler>> GetEventHandler(this object value)
{
    if (value == null)
    {
        return null;
    }

    return from fieldInfo in value.GetType().GetFields(NonPublicInstance)
           where typeof(EventHandler).IsAssignableFrom(fieldInfo.FieldType)
           select new Tuple<string, EventHandler>(fieldInfo.Name, (EventHandler)fieldInfo.GetValue(value));
}

This breaks the build, since my tests are expecting a single EventHandler, not a collection of Tuples.  I could go try to fix the tests at this point, but how can I?  The tests all use VerifyInvocationList, which also expects a single Delegate.   It looks like I’m going to end up either refactoring my Verify method or creating another Verify method to handle a Tuple collection.  Also this line is starting to bother me, its too long and it has too much punctuation.

select new Tuple<string, EventHandler>(fieldInfo.Name, (EventHandler)fieldInfo.GetValue(value));

Looking at this line, I want to create a data transfer class to hold onto my two pieces of data, getting rid of the Tuple.  If I follow that urge, then I should do it now, before updating the tests, otherwise I’ll end up changing the tests twice.

The DTO doesn’t need to be complicated, this should suffice for now.

public class EventCallback
{
    public EventCallback(string name, Delegate callback)
    {
        this.Name = name;
        this.Callback = callback;
    }

    public Delegate Callback { get; set; }
    public string Name { get; set; }
}

After this modification, the projection is cleaner, but I think it still suffers from an excess of parens.

select new EventCallback(fieldInfo.Name, (EventHandler)fieldInfo.GetValue(value));

I’ll create an extension method that lets me replace some of those parens with angles.

public static T GetValue<T>(this FieldInfo fi, object value)
{
    return (T)fi.GetValue(value);
}

Now I can rewrite my projection like this.

select new EventCallback(fieldInfo.Name, fieldInfo.GetValue<EventHandler>(value));

Ok that was fun, but my build is still broken.  I’ll give the tests some love next, but I need to make one more update to this extension method.  I’ll change its name to GetEventHandlers to reflect that it returns a collection.

As mentioned above, the most immediate problem with the tests are that they are not expecting collections.  In some tests, I resolve this issue by switching to the var keyword, and in others I need to introduce a local variable to hold onto the new collection.  But, as expected, the build is still broken because the Verify method wont take a collection.  I’ll stop using my custom verifier and go back to ApprovalTest basics.  Here is a new version of GetPocoEventInvocationList.

[TestMethod]
public void GetPocoEventInvocationList()
{
    var poco = new Poco();
    poco.ProcessCompleted += Domain.HandleProcessCompleted;

    var pocoDelegates = poco.GetEventHandlers();
    Approvals.VerifyAll(pocoDelegates, string.Empty);
}

II have to comment out a line in a broken test so that I can run this and I don’t like the result, it doesn’t tell me any useful information.

[0] = EventReflection.Demo.EventCallback

I can pass a formatter to VerifyAll, or I can override EventCallback.ToString to provide a nicely formatted string representation.  The ability to override ToString is an advantage I gained when I decided to use a custom DTO, that I wouldn’t have if I had stuck with the Tuple.  Eventually, I will override ToString, but for now I’ll just use a formatter until I get the formatting I want, then I’ll move that formatter into EventCallback.

Here are the results from my first attempt:

ProcessCompleted => [Void HandleProcessCompleted(System.Object, System.EventArgs)]

And the code which produces it:

Approvals.VerifyAll(
    pocoDelegates, 
    e => "{0} => {1}".FormatWith(e.Name, e.Callback.GetInvocationList().Select(d => d.Method).ToReadableString()));

This is an improvement, but both the result and the code are a little complicated for my taste.  I liked the vertical spacing that VerifyInvocationList provided more than the horizontal formatting ToReadableString provides.  The formatter is too long, an it repeats code already in VerifyInvocationList.

Second attempt looks much nicer.  It takes better advantage of vertical space and retains the index numbers for each method in the invocation list.  This is actually important information, since the delegates will be executed in the order that they appear in the invocation list:

ProcessCompleted
    [0] Void HandleProcessCompleted(System.Object, System.EventArgs)

Here is the formatter which produces this output.

e =>
{
    var buffer = new StringBuilder();
    buffer.AppendLine(e.Name);
    var delegates = e.Callback.GetInvocationList();
    for (int index = 0; index < delegates.Length; index++)
    {
        var d = delegates[index];
        buffer.AppendLine("\t[{0}] {1}".FormatWith(index, d.Method));
    }
    return buffer.ToString();
}

I use a buffer to hold onto the data as I build the results.  It’s pretty straightforward, although you might not be familiar with the extension method FormatWith.  This extension ships with the ApprovalUtilities library that accompanies ApprovalTests, and I find it’s a cleaner way to call string.Format.  I like the new formatting, so I’ll make this formatter into the ToString implementation for EventCallback.  Then I can simplify my test, I keep a simple formatter, to prevent VerifyAll from prefixing additional index numbers to each result.

[TestMethod]
public void GetPocoEventInvocationList()
{
    var poco = new Poco();
    poco.ProcessCompleted += Domain.HandleProcessCompleted;

    var pocoDelegates = poco.GetEventHandlers();
    Approvals.VerifyAll(pocoDelegates, e => e.ToString());
}

And I can update my other failing test as well:

[TestMethod]
public void PocoClientListensToPoco()
{
    Poco poco = new Poco();
    PocoClient client = new PocoClient(poco);
    var eventHandlers = poco.GetEventHandlers();
    Approvals.VerifyAll(eventHandlers, e => e.ToString());
}

Of course, now I have duplication, and I have a weird little formatter there, which I’ll need to remember if I want to get these results every time.  So I should encapsulate that duplication so I don’t need to remember it anymore.

using System.Collections.Generic;
using ApprovalTests;

public static class EventUtility
{
    public static void VerifyEventCallbacks(IEnumerable<EventCallback> callbacks)
    {
        Approvals.VerifyAll(callbacks, c => c.ToString());
    }
}

And I can update my tests.

[TestMethod]
public void PocoClientListensToPoco()
{
    Poco poco = new Poco();
    PocoClient client = new PocoClient(poco);
    EventUtility.VerifyEventCallbacks(poco.GetEventHandlers());
}

That was a bit of work to get rid of the magic string.  However, it should be worth it because the new query will also detect new event handlers when they’re added.  My current tests don’t verify this behavior.  Also, I had some tests against VerifyInvocationList that showed multiple handlers wired up to one delegate, but that scenario isn’t covered yet with VerifyEventCallbacks.

To get these scenarios covered, I’ll add another event to Poco.  I’m still assuming that I don’t control Poco, but we just transitioned into an alternate reality where Poco always had more than one event.  I’ll call the new event ProcessStarted, so here’s what we’ve got now.

public event EventHandler ProcessCompleted;
public event EventHandler ProcessStarted;

Simply adding this event declaration is enough to break two of my tests.  My first thought is that it is an approval failure because the new event is already detected even though I hadn’t wired anything up yet.  However, it turns out that EventCallback.ToString is throwing a null reference exception.  Of course, this is because the backing field for an event will be null until the first event is wired up.  The event was detected, but since it was null, the Callback property was also null.  Because the delegate is null, I get a null reference exception on GetInvocationList.

After considering my options, I decide to filter out the nulls in the query.  I don’t want to list any events that aren’t wired up anyway, they would just be noise.  To avoid reflecting into my object twice, I’ll introduce a new variable in the query using the let keyword, then filter out nulls.

from fieldInfo in value.GetType().GetFields(NonPublicInstance)
where typeof(EventHandler).IsAssignableFrom(fieldInfo.FieldType)
let callback = fieldInfo.GetValue<EventHandler>(value)
where callback != null
select new EventCallback(fieldInfo.Name, callback);

This fixes my existing tests, but I still need to cover my missing scenarios.

[TestMethod]
public void MultipleEvents()
{
    var poco = new Poco();
    poco.ProcessCompleted += Domain.HandleProcessCompleted;
    poco.ProcessStarted += Domain.HandleProcessStarted;
    EventUtility.VerifyEventCallbacks(poco.GetEventHandlers());
}

This test produces these results, which look good so I approve them

ProcessCompleted
    [0] Void HandleProcessCompleted(System.Object, System.EventArgs)

ProcessStarted
    [0] Void HandleProcessStarted(System.Object, System.EventArgs)

And one more test to cover the multicast scenario.

[TestMethod]
public void MulticastEvent()
{
    var poco = new Poco();
    poco.ProcessCompleted += Domain.HandleProcessCompleted;
    poco.ProcessCompleted += Domain.HandleProcessStarted;
    EventUtility.VerifyEventCallbacks(poco.GetEventHandlers());
}

And this test produces good results too.

ProcessCompleted
    [0] Void HandleProcessCompleted(System.Object, System.EventArgs)
    [1] Void HandleProcessStarted(System.Object, System.EventArgs)

I’m pretty happy with that result. But I still have a problem, I have to look at the test to figure out which object these event handlers are attached to. The result could be improved if they included some description where they came from. This shouldn’t be too hard to fix.

First I’ll change the VerifyEventCallbacks implementation to take the object itself, rather than the collection of callbacks.  Once I have the object I can query it for the type name, then query it for the callbacks, then collect it all into a buffer.

public static void VerifyEventCallbacks(object value)
{
    var buffer = new StringBuilder();
    buffer.AppendLine("Event callbacks for {0}".FormatWith(value.GetType().Name)).AppendLine();
    foreach (var callback in value.GetEventHandlers())
    {
        buffer.AppendLine(callback.ToString());
    }

    Approvals.Verify(buffer);
}

Then I need to update my tests to pass the object instead of a collection.

[TestMethod]
public void GetPocoEventInvocationList()
{
    var poco = new Poco();
    poco.ProcessCompleted += Domain.HandleProcessCompleted;
    EventUtility.VerifyEventCallbacks(poco);
}

Which produces results that indicate where the events are are attached:

Event callbacks for Poco

ProcessCompleted
    [0] Void HandleProcessCompleted(System.Object, System.EventArgs)

That’s pretty nice.  There’s always another problem though.  The new implementation queries for the value’s type before the null check in GetEventHandlers.   It probably can’t handle nulls.  I’ll write a test to confirm and fix.

[TestMethod]
public void NullHasNoEvents()
{
    EventUtility.VerifyEventCallbacks(null);
}

As expected this generates a null reference exception.  Approvals.Verify can actually handle null just fine, so if value is null, I just need to make sure that a null gets verified.  This update to VerifyEventCallbacks does the trick.

public static void VerifyEventCallbacks(object value)
{
    StringBuilder buffer = null;
    if (value != null)
    {
        buffer = new StringBuilder();

        buffer.AppendLine("Event callbacks for {0}".FormatWith(value.GetType().Name)).AppendLine();
        foreach (var callback in value.GetEventHandlers())
        {
            buffer.AppendLine(callback.ToString());
        }
    }

    Approvals.Verify(buffer);
}

This works as expected, which is to say that when Approvals.Verify receives the null buffer, it produces an empty result file. With this last change I think I’m done for now.  There might still be ways to make this code better, but nothing it popping out at the moment.  This is a good time to pause and review.

When I create my test target and call VerifyEventCallbacks the results tell me this:

  1. The target type.
  2. Of all the possible events on the type, which had handlers attached.
  3. For each event, the handlers are listed in the order they execute.

Relationship with EventApprovals

The EventApprovals DTO is a little different than EventCallback.  Its called CallbackDescriptor and while it also includes the event’s name, it holds on to the projected MethodInfo collection  instead of holding on to the delegate.

In EventApprovals, you can get the collection of POCO event callbacks using ApprovalUtilities.Reflection.GetPocoEvents.  Normally you don’t need to access the event collection at that level.  If you just want to verify the event inventory, the closest method is ApprovalTests.Events.EventApprovals.VerifyEvents, its not exactly the same because it actually does more than shown here—it can inventory WinForm events as well.

Up Next

As I mentioned at the top, the reflection necessary to retrieve the backing delegate seems fairly simple.  In the case of Poco, it turned out to be straightforward.  But things might not be as simple as they seem.  All the events on Poco are based on EventHandler, what about other delegate types?  Furthermore, all the events on Poco are declared directly on Poco, what if we also had to find events inherited from a base class?  I probably wouldn’t bring it up if what I’ve shown so far worked in those scenarios, so in the next installment “Beyond the Event Horizon: Event Complications” I’ll see if I can overcome these obstacles.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s