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:
- The class variables. More complex unit test class. Have to coordinate these variables across multiple functions.
- 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!
- Have to have two functions to do something really, really simple.
- 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?
- 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.
- Added 5,000ms timeout to the WaitOne() function to prevent hanging of unit tests.