How to mock out event calls in NMock?
When it comes to .NET eventing, a lot of developers barf at and not knowing how to test them. one is that the event handler methods are correctly wired to the corresponding events, and two the event handler code does what it's supposed to. The first is hard to test, because the wiring of a method to the event is internal to the containing class only. The second is easier, because you could stub out the event handler method and making sure that it is getting called, but pure mockists would dislike this approach.
Consider the following example:
public interface IBattery {
event EventHandler Low;
event EventHandler Depleted;
}
public class Battery : IBattery {
public event EventHandler Low;
public event EventHandler Depleted;
public Battery() {
Low += new EventHandler(OnLowBattery);
Depleted += new EventHandler(OnDepleted);
}
public void SomeMethodThatConsumesBattery() {
.
.
.
if (IsLowBatter("10%")) {
OnLowBattery(this, EventArgs.Empty);
}
}
protected virtual void OnLowBattery(object sender, EventArgs e) {
if (Low!= null) {
Low(sender, e);
}
}
}
public interface ILaptop { }
public class Laptop : ILaptop {
private IBattery battery;
public Laptop(IBattery battery) {
this.battery = battery;
batter.Low += new EventHandler(OnLowBattery);
}
protected virtual void OnLowBattery(object sender, EventArgs e) {
// Count down 15 mins before shutdown!
}
}
We want to unit test the events are wired for our Laptop class, but we want to avoid changing anything any of the classes for the purpose of unit testing, since I am a big advocate of "never change your production code just for testing." You may argue that I have a "protected virtual void" event handler method there to leave room for myself for stubbing, but as far as event handling method goes, I think it is actually a good idea to allow my subclasses to override and extend my default implementation. This is the standard and encouraged way of writing and declaring event handling methods too in writing custom ASP.NET server controls. Check out Nikhil Kothari's book.
So, solution one, create a stub class for our Battery class in testing Laptop, and add extra methods to manually raise the events:
public class BatteryStub : Battery {
privately bool lowBatteryExecuted = false;
public void RaiseLowBattery() {
Low(this, EventArgs.Empty);
}
protected override void OnLowBattery(object sender, EventArgs e) {
lowBatteryExecuted = true;
}
public void Verify()
{
if (!lowBatteryExecuted) throw new ApplicationException();
}
}
[TestFixture]
public class LaptopTests {
[Test]
public void StartCountdownWhenLowBattery() {
IBattery batteryStub = new BatteryStub();
ILaptop laptop = new Laptop(batteryStub);
batteryStub.RaiseLowBattery();
// Assert laptop countdown started.
battery.Verify();
}
}
That's a lot of code to just unit test a single event is wired correctly. Also this stub is really doing a lot of a mock's work. Look at the Verify() and the simplistic stubbed event handler method. The fact that they are there and are simply is because in testing we are not interested in what they do, but that they are getting called. Now, duplicate this kind of test stub class for every object you have events, and you will quickly lose appetite on how many you have to write for all your domain objects.
Fortuntely there is a second solution, if you use NMock 1.1:
(credit to my co-worker and talented friend Levi Khatskevitch)
public class DynamicEventMock : DynamicMock
{
private const string ADD_PREFIX = "add_";
private const string REMOVE_PREFIX = "remove_";
private EventHandlerList handlers = new EventHandlerList();
public override object Invoke(string methodName, params object[] args)
{
if (methodName.StartsWith(ADD_PREFIX))
{
handlers.AddHandler(GetKey(methodName, ADD_PREFIX), (Delegate) args[0]);
return null;
}
if (methodName.StartsWith(REMOVE_PREFIX))
{
handlers.RemoveHandler(GetKey(methodName, REMOVE_PREFIX), (Delegate) args[0]);
return null;
}
return base.Invoke(methodName, args);
}
public void RaiseEvent(string eventName, params object[] args)
{
Delegate handler = handlers[eventName];
if (handler == null)
{
if (mockedType.GetEvent(eventName) == null)
{
throw new MissingMemberException("Event " + eventName + " is not defined");
}
else if (Strict)
{
throw new ApplicationException("Event " + eventName + " is not handled");
}
}
handler.DynamicInvoke(args);
}
private static string GetKey(string methodName, string prefix)
{
return string.Intern(methodName.Substring(prefix.Length));
}
}
Now, in your test class:
[TestFixture]
public class LaptopTests {
[Test]
public void BatteryLowIsRaised()
{
DynamicEventMock mockBattery = new DynamicEventMock(typeof(IBattery));
ILaptop laptop = new Laptop((IBattery)mockBattery.MockInstance);
mockBattery.RaiseEvent("PlayClick", EventArgs.Empty);
// Assert the laptop instance's 15 mins count down started
mockBattery.Verify();
}
}
Should the Low event wasn't wired, this test fails. You have explicitly told the mock object to raise its event, and assuming your events are wired correctly to the correct event handler, you have control over when to fire them.
There are drawbacks, of course. Notice the event name is represented by a string, making event renaming a pain to do, as it is for NMock for method expectations. My only advise is try out EasyMock.NET if that is a real pain for you.
1 comment:
Thanks for the info, Stephen. We are taking this into use on the Suncor project here in Calgary!
Post a Comment