[Tip Jar] Unit Test Events With Anonymous Delegates

Here we are already looking ahead to learn about the language features of C# 3.0 and I am still finding new ways to make my code better with good “old fashioned” C# 2.0.

Like many people, I tend to fall into certain habits of writing code. For example, today I was writing a unit test to test the source of a particular event. I wanted to make sure that the event is raised and that the event arguments were set properly. Here’s the test I started off with (some details changed for brevity) which reflects how I would do this in the old days.

private bool eventRaised = false;

[Test]
public void SettingValueRaisesEvent()
{
    Parameter param = new Parameter("num", "int", "1");
    param.ValueChanged += OnValueChanged;
    parameter.Value = "42"; //should fire event.

    Assert.IsTrue(eventRaised, "Event was not raised");
}

void OnValueChanged(object sender, ValueChangedEventArgs e)
{
    Assert.AreEqual("42", e.NewValue);
    Assert.AreEqual("1", e.OldValue);
    Assert.AreEqual("num", e.ParameterName);
    eventRaised = true;
}

A couple of things rub me the wrong way with this code.

First, I do not like relying on the member variable eventRaised because another test could inadverdently set that value, unless I make sure to reset it in the SetUp method. So now I need a SetUp method.

Second, I don’t like the fact that this test requires this separate event handler method, OnValueChanged. Ideally, I would prefer that the unit test be self contained as much as possible.

Then it hits me. Of course! I should use an anonymous delegate to handle that event. Here is the revised version.

[Test]
public void SettingValueRaisesEvent()
{
    bool eventRaised = false;
    Parameter param = new Parameter("num", "int", "1");
    param.ValueChanged += 
        delegate(object sender, ValueChangedEventArgs e)
        {
            Assert.AreEqual("42", e.NewValue);
            Assert.AreEqual("1", e.OldValue);
            Assert.AreEqual("num", e.ParameterName);
            eventRaised = true;
        };
    param.Value = "42"; //should fire event.

    Assert.IsTrue(eventRaised, "Event was not raised");
}

Now my unit test is completely self-contained in a single method. Lovely!

In general, I try not to use anonymous delegates all over the place, especially delegates with a lot of code. I think they can become confusing and hard to read. But this is a situation in which I think using an anonymous delegate is particularly elegant.

Contrast this approach to the approach using Rhino Mocks I wrote about a while ago. In that scenario, I was testing that a subscriber to an event handles it properly. In this case, I am testing the event source.

Technorati Tags: , , ,

What others have said

Requesting Gravatar... Jeremy Miller Dec 13, 2006 5:52 PM
# re: [Tip Jar] Unit Test Events With Anonymous Delegates
I'd second that tip. I like to use anonymous delegates now for testing asynchronous code to stick a ManualResetEvent.Set() inside the block that's called from a callback event, start the action, and then make a ManualResetEvent.WaitOne() call below that in the NUnit test to wait for the asynchronous action.

As for using RhinoMocks to mock events, my team did a near mutiny on me early on because they didn't like the syntax. We've started to use recording stubs in place of the views more often when we test our controllers.
Requesting Gravatar... you've been HAACKED Dec 14, 2006 11:59 AM
# Indulging My OCD Using TestDriven.NET With NCoverExplorer
Indulging My OCD Using TestDriven.NET With NCoverExplorer
Requesting Gravatar... Jason Bock Dec 14, 2006 7:39 PM
# re: [Tip Jar] Unit Test Events With Anonymous Delegates
Ditto, I do this all the time, esp. with asynchronous code (same idea that Jeremy mentioned). I so miss this feature in VB 8 (unless I missed it).
Requesting Gravatar... Sontek (John M. Anderson) Dec 20, 2006 3:30 PM
# Unit testing events with anonymous methods
Unit testing events with anonymous methods
Requesting Gravatar... Julien Couvreur Sep 04, 2007 12:46 PM
# re: [Tip Jar] Unit Test Events With Anonymous Delegates
One limitation with the example that you explain is that it's not truly asynchronous. You know exactly when the event is supposed to be raised.
Things get trickier if the callback could happen a little later. That's were you need some kind of synchronization primitive like ManualResetEvent as Jeremy was saying.
To handle that case, I found a generic solution which takes care of the synchronization for you and also works with more complicated scenarios. You may be interested to read some details at http://blog.monstuff.com/archives/000323.html

Cheers,
Julien

What do you have to say?

(will show your gravatar)
Please add 4 and 1 and type the answer here: