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.

Popularity: 9% [?]

  1. Excellent one..thanx for the insight that u describe through this article ..:-)

  2. Thanks for the tip, it’s one that I’ll remember.

    Or, barring that, one I’ll return to with my new bookmark. :)

  3. Brilliant. This code was a great help.
    Thanks very much.
    Rob

  4. Madhuri Venkatesh

    This is really smart. Thanks, but there could be a memory leak if we do not un-register the event-handler.
    See http://bytes.com/topic/c-sharp/answers/276123-memory-leak-c-2-0-w-anonymous-delegates

  5. 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();
    }

  6. 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.

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>