I want my unit tests to be able to verify that some events were subscribed or unsubscribed at specific points. Currently, I see two options.
VerifiableEventHandler
Each client subscribes using the common +=
and -=
notation. Each class that has a verifiable eventhandler needs to implement IsSubscribed(), which can query the eventhandler using GetInvocationList().
public interface IVerifiableEventHandler<T>
{
event EventHandler<T> EventHandler;
bool IsSubscribed(Delegate d);
}
// impl
public bool IsSubscribed(Delegate d)
{
return EventHandler.GetInvocationList().Contains(d);
}
Custom subscription interface
The actual C# event handler would be hidden, and clients can only subscribe using the Subscribe / Unsubscribe function.
public interface IEventSource<T>
{
void Subscribe(EventHandler<T> func);
void Unsubscribe(EventHandler<T> func);
void Raise(object sender, T event_args);
}
Pros and Contras
To me, the advantage of the first approach is that the principle of least surprise is not violated. A .Net programmer will expect events to be subscribed to using the +=
notation.
The advantage of the second approach is that it's very easy to test, as the functions are nothing special and can be verified easily if the class containing the eventhandler is mocked.
Is there a better way to solve this problem? If not, which of the two variants should be preferred, and why?
2 Answers 2
There's a third option: mock the object with the event and use behavior testing to verify the mock's subscriptions at given points. Several mocking frameworks allow for this.
public interface INeedToBeMocked
{
public event EventHandler EventRaised;
}
NSubstitute
var mockedItem = Substitute.For<INeedToBeMocked>();
mockedItem.Received().EventRaised += Arg.Any<EventHandler>();
Rhino Mocks
var mockedItem = MockRepository.GenerateMock<INeedToBeMocked>();
mockedItem.AssertWasCalled(x => x.EventRaised += Arg.Is.Anything);
-
Hm, this option is unfortunately absent from Ninject, as far as I can tell. Still, it does answer my question.Wilbert– Wilbert2014年04月16日 09:08:04 +00:00Commented Apr 16, 2014 at 9:08
-
You should be able to do with this Ninject and a mocking framework. One option is to use something like
ninjectObject.Bind<INeedToBeMocked>().ToConstant(mockedObject);
during your testing.Dan Lyons– Dan Lyons2014年04月16日 17:11:27 +00:00Commented Apr 16, 2014 at 17:11 -
My mistake, I mixed it up. I meant Moq.Wilbert– Wilbert2014年04月16日 20:01:26 +00:00Commented Apr 16, 2014 at 20:01
Is there a better way to solve this problem?
Yes!
C# allows you to override the +=
and -=
syntax. I'm not sure how easy it is to supply in common mocking frameworks like Moq, but it should be trivial to build your own fake object that has hooks into the subscribe and unsubscribe methods of your interface:
private Action foo = () => {};
public event Action Foo
{
add
{
Console.WriteLine("Subscribe called with value: {0}", value);
foo += value;
}
remove
{
Console.WriteLine("Unsubscribe called with value: {0}", value);
foo -= value;
}
}
(though you'll likely to want to do something more interesting in the properties)
-
This works only if I want to test the actual event source object. If I want to test in another context that something subscribes, I cannot mock the class containing, which means I would have to manually implement a counting event handler...Wilbert– Wilbert2014年04月15日 13:26:51 +00:00Commented Apr 15, 2014 at 13:26
-
@Wilbert - sorry, that is not clear. Could you clarify?Telastyn– Telastyn2014年04月15日 13:36:29 +00:00Commented Apr 15, 2014 at 13:36
-
Class A has an event that can be subscribed to. Class B wants to subscribe and unsubscribe. In the IEventSource<> case, I can simply mock A in the unit test of class B, and verify that _a.Subscribe() was called with the correct param. Custom add/remove will only help me in the unit test for A, but not help me test that B subscribed to A in the test for B.Wilbert– Wilbert2014年04月15日 13:46:04 +00:00Commented Apr 15, 2014 at 13:46
-
I could of course implement a version of IA that uses sub counting or so and use this in the unit test for B, but that is far less elegant and much more code than simply calling mock.Verify...Wilbert– Wilbert2014年04月15日 13:47:34 +00:00Commented Apr 15, 2014 at 13:47
-
@Wilbert - A little more code maybe, but it's far better than changing your interface to support testing alone.Telastyn– Telastyn2014年04月15日 14:11:40 +00:00Commented Apr 15, 2014 at 14:11