This is a story about the pain of testing events in C#. If you don’t have time to read the whole story, but you are looking for an easy, reliable way to test (asynchronous) events, you can skip straight to the end.
The story
So I’ve been unit-testing events in C# lately, and it’s been kind of a pain. Let’s say we have this code:
And we want to know whether we got the correct answer. We could write a unit test that looks something like this:
Although–doesn’t this cause a memory leak? Actually, no, because both the producer of, and the subscriber to the event, have the same life cycle and can be garbage collected together. Still, one could argue (and many people do) that it’s good practice to explicitly unsubscribe from the event anyway: that way, you’ve got your bases covered if you do run into a situation where such a memory leak would occur. Or even worse: you could refactor your code, change the scope of either the finder or the event handler, and you forget to update all the places where either of them is used, and voilà: you’ve introduced a memory leak and you didn’t even know it. Better safe than sorry, right?
So now we end up with something like this:
That will do the trick, right? Wrong. There’s no guarantee an answer will be found. DeepThought.Compute() may simply give up at some point and return null, and the event will never be raised. Unfortunately, in that case, our assert is never executed, and the test will happily report success. So we have to guard against that:
Ouch. Also, what does that -1 mean? Assuming that the answer is always an integer, are we sure it can’t be negative? I’m certainly not. Also, we’re conflating two properties here: whether or not the event was raised, and the actual answer. Let’s fix that.
That’s an awful lot of boilerplate code for such a simple test! In fact, I’m having trouble seeing the actual test logic through all the boilerplate. Now, imagine if you have a comprehensive test suite with many tests like these. Or actually: please don’t. It makes me sad.
What makes me even sadder, is that DeepThought.Compute() is quite an expensive operation. We don’t want it to freeze the GUI; we should off-load it to a background thread. Great: now we have an asynchronous event. Let’s replace the bool with a ManualResetEventSlim:
OK, that’s not so bad, right? But wait: ManualResetEventSlim implements IDisposable!
Of course we could lessen that load by turning resetEvent into a field, and using [SetUp] and [TearDown] methods to initialize it and clean it up. But still a lot of boilerplate remains.
EDIT Are we done? I thought so, but as Ralph correctly pointed out in the comments, this still doesn’t work. If the assert fails, it will throw an exception, yes–but it will do so in the background thread, not in NUnit’s main thread. This means that NUnit doesn’t pick up on it, and the behaviour that follows is undefined. It could report success, or it could fail to terminate at all. The only thing you can be certain of, is that the test won’t actually fail in the way you want. So we have to move the assertion back into the main thread:
And now, we’re stuck with that nasty -1 again.
Thankfully, we can do better. A lot better.
A solution
I’ve written a small class that uses some clever reflection tricks to handle most of the boilerplate I’ve shown above. Behold:
Neat, isn’t it? You can find the code below. Maybe I’ll do a proper version on GitHub some day, with its own test suite and proper documentation. Until then, this blog post will have to do :).
Here’s a few things EventMonitor can do:
It works both for synchronous and asynchronous events.
You can give it a custom timeout in an optional constructor parameter. By default, it’ll wait for 500 milliseconds.
Even if you decide not to use the conventional object sender, EventArgs e delegates, it will work with any delegate with up to 4 parameters, as long as they have a void return type.
If you want to check that the same event is fired twice, you can do that as follows:
Here’s a few things EventMonitor unfortunately can’t do:
Unfortunately, it doesn’t seem possible in C# to pass an event as a parameter. That’s why I opted for the “stringly typed” option of passing the event’s name as a string. If you know of a better way to handle this, I would very much like to hear about it!
It’s not fully thread-safe. If you have asynchronous events, and several of them are raised at the same time, EventMonitor’s behaviour will be undefined. If you raise only one asynchronous event, or if you ensure that they aren’t raised simultaneously, it will be fine. This issue could be solved by employing a lock in the Handle method.
Update, October 16th, 2013
Fixed the code, so that it will handle event delegates with primitive type arguments, as well as object type arguments, by adding generics to the solution, as suggested in this StackOverflow question.