Easily Unit Testing Event Handlers

In C#, If you need to unit test a class that fires an event in certain circumstances (perhaps even asynchronously), you need to handle a little more than just running some code and doing the assertion. You have to make sure your unit test waits for the event to be fired. Here’s one naive way of doing it, a WRONG way:

   1: private bool statsUpdated = false;
   2: private ManualResetEvent statsUpdatedEvent = new ManualResetEvent(false);
   3:
   4: [Test]
   5: public void CheckStats()
   6: {
   7:     BrickDatabase db = new BrickDatabase(tempFolder, maxCacheAge);
   8:
   9:     statsUpdated = false;
  10:     statsUpdatedEvent.Reset();
  11:
  12:     db.InventoryStatsUpdated += new EventHandler(db_InventoryStatsUpdated);
  13:     db.DoSomethingThatFiresEvent();
  14:
  15:     statsUpdatedEvent.WaitOne();
  16:
  17:     Assert.IsTrue(statsUpdated);
  18: }
  19:
  20: void db_InventoryStatsUpdated(object sender, EventArgs e)
  21: {
  22:     statsUpdated = true;
  23:     statsUpdatedEvent.Set();
  24: }

There are a number of things wrong with this:

  1. The class variables. More complex unit test class. Have to coordinate these variables across multiple functions.
  2. Since they are class variables, you will want to reuse them, but you’d better remember to reset the event and the boolean every time!
  3. Have to have two functions to do something really, really simple.
  4. The WaitOne() does not have a timeout, so if the wait is ever satisfied then statsUpdated is guaranteed to be true.

Here’s a better way of doing it, using anonymous methods in C# 2.0:

   1: [Test]
   2: public void CheckStats()
   3: {
   4:     BrickDatabase db = new BrickDatabase(tempFolder, maxCacheAge);
   5:     bool statsUpdated = false;
   6:     ManualResetEvent statsUpdatedEvent = new ManualResetEvent(false);
   7:
   8:     db.InventoryStatsUpdated += delegate
   9:     {
  10:         statsUpdated = true;
  11:         statsUpdatedEvent.Set();
  12:     };
  13:
  14:     db.DoSomethingThatFiresEvent();
  15:
  16:     statsUpdatedEvent.WaitOne(5000,false);
  17:
  18:     Assert.IsTrue(statsUpdated);
  19: }

Improvements?

  1. The event is just part of the method. Since the event handler is an anonymous delegate, it can access the enclosing method’s local variables.
  2. Added 5,000ms timeout to the WaitOne() function to prevent hanging of unit tests.

8 thoughts on “Easily Unit Testing Event Handlers

  1. Matthew Oberlin

    There is actually an easier way to do this using anonymous methods…


    [Test]
    public void CheckStats()
    {
    BrickDatabase db = new BrickDatabase(tempFolder, maxCacheAge);

    db.InventoryStatsUpdated += delegate
    {
    Assert.IsTrue(true);
    };

    db.DoSomethingThatFiresEvent();
    }

  2. Ben Post author

    Matthew, I don’t think that will work because if that event handler is not called, then no assertion will be raised, thus the test passes erroneously.

  3. Kicki

    Madhuri, there won’t be a memory leak, since the BrickDataBase object is local to the test method, and will be released for gc at the end of the method.

  4. matt smith

    [Test]
    public void CheckEventFired()
    {
    // Arrange
    Player player = new Player();
    bool eventFired = false;

    Player.OnHealthChange += delegate
    {
    eventFired = true;
    };

    // Act
    player.AddHealth(0.1f);

    // Assert
    Assert.IsTrue(eventFired);
    }

    this seems to work for me – only passes when my AddHealth(…) method called (that publishes event)

    .. mat ..

Comments are closed.