December 21, 2008 20:17
So here is the method on Brain class we're going to test:
public void TouchIron(Iron iron)
{
try {
_hand.TouchIron(iron);
}
catch(BurnException) {
_mouth.Yell();
}
}
We need to test the brain in isolation ensuring it yells if a hot iron is passed. In the test, the hand should emulate the hot iron case, and mouth should yell exactly once before the method finishes [1].
[Test]
public void TouchHotIron_Yell()
{
using (var mockery = new Mockery()) {
var hand = (IHand)mockery.NewMock(typeof(IHand));
var mouth = (IMouth)mockery.NewMock(typeof(IMouth));
Expect.Once.On(hand).Method("TouchIron").With(Is.Anything).Will(Throw.Exception(new BurnException()));
Expect.Once.On(mouth).Method("Yell");
var brain = new Brain(hand, mouth);
brain.TouchIron(new Iron());
}
}
Nice thing is that the main Mockery object can be wrapped in a using statement so that its Dispose method automatically verifies that all expectations have been met. A clear sign that this mockery should be a private field being initialized in SetUp and disposed in TearDown.
Pros and cons.
The framework is an open source project (distributed under Apache 2 license). It's actively in development: for now, last update on December,
6. It can mock interfaces, properties, events, and methods (even supports out parameters). Yet not able to mock classes but that’s
coming soon. From the syntax perspective NMock2 is quite expressive, neat,
and fairly easy to learn [2].
It uses Remoting to hook onto methods it mocks.
Internal error messages are usually clear and
understandable (believe me it matters not only for a newbie). Say, if
neither TouchIron nor Yell has been called, we’d see
NMock2.Internal.ExpectationException: not all expected invocations were performed
Expected:
1 time: hand.TouchIron(equal to <Test.Iron>) [called 0 times]
1 time: mouth.Yell(any arguments) [called 0 times]
Note that only strict mocks are supported: if you
put an expectation on a method, the framework throws an exception if
the method hasn’t been called. However, there’s a way to define a stub
for all methods/properties/etc:
Stub.On(mock).Method(new AlwaysMatcher(true, "some description to be used in NMock2 messages"));
Yes, it makes a difference between stubs and expectations (a
stub is actually the "at least once" expectation).
Basically, expectations are things that have to happen and stubs are
things that are needed for the operation to succeed (so NMock2 never
verifies expectations on stubs).
Improvements: NMock2 + C#3.
As you may see, unfortunately the framework uses string names for mocking, so it
is neither type safe nor refactoring friendly. However, with lambdas
and extension methods, we can easily improve that [3]. Look at the "original" syntax followed by the "new" one:
Expect.Once.
On(hand).Method("TouchIron").
With(Is.Anything).
Will(Throw.Exception(new BurnException()));
Expect.Once.
On(hand).Method<Iron>(hand.TouchIron).
With(Is.Anything).
Will(Throw.Exception(new BurnException()));
It’s a good starting point if the only thing you need is type safety (however, it probably could be improved in the Method<Iron>(hand.TouchIron) part). Any further ideas are welcome!
And here are the extensions:
internal static class TypeSafetyExtensions
{
internal static IMethodSyntax On<Caller>(this IReceiverSyntax syntax, Caller x)
{
return syntax.On(x);
}
internal static IArgumentSyntax Method<Parameter>(this IMethodSyntax syntax, Action<Parameter> action)
{
return syntax.Method(action.Method.Name);
}
internal static IArgumentSyntax Method(this IMethodSyntax syntax, Action action)
{
return syntax.Method(action.Method.Name);
}
}
Sum up.
Pros:
1. Open source and free.
2. Expressive and easy to learn expectations DSL.
3. Clear error messages.
Cons:
1. Cannot mock classes (yet).
2. Expectations are string based.
3. No way to turn off Strict mocks.
Footnotes:
[1] – Here is the code for the types used in the example.
[2] – Here is a nice cheat sheet on the syntax.
[3] – Andrey Shchekin suggests another idea about stringless expectations (a more "Rhino Mocks like" one).