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.
Excellent one..thanx for the insight that u describe through this article ..:-)
Thanks for the tip, it’s one that I’ll remember.
Or, barring that, one I’ll return to with my new bookmark. 🙂
Brilliant. This code was a great help.
Thanks very much.
Rob
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
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();
}
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.
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.
[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 ..