A while ago, in order to understand Rhino
Mocks you had to get into its Record/Replay model: record the
expectations first, and then playback the scenario using the mocked
objects.
This has been claimed as clumsy and weird by a lot of people
(especially those who don’t know much about unit testing). Once you get used to
it, it becomes sort of OK, but still it's been considered as a chink in Rhino armor.
In "old" Rhino
Mocks our touch-and-yell
test[1] looks like
[Test]
public void TouchHotIron_Yell()
{
var mocks = new MockRepository();
var hand = mocks.StrictMock<IHand>();
var mouth = mocks.StrictMock<IMouth>();
using (mocks.Record()) {
Expect.Call(() => hand.TouchIron(null)).Constraints(Is.Anything()).Throw(new BurnException());
Expect.Call(mouth.Yell);
}
using (mocks.Playback()) {
var brain = new Brain(hand, mouth);
brain.TouchIron(new Iron { IsHot = true });
}
}
Keeping up with the Joneses.
Starting from Rhino Mocks 3.5 you don't have
to use Record/Replay: a new approach called AAA (Arrange, Act, Assert)
is in town.
As in all other mocking
frameworks, now we can generate stubs and mocks in a neat and simple way that requires a mere Assert and maybe VerifyAllExpectations to make sure the methods you expected to call have actually been called.
With the new syntax, our test looks like
[Test]
public void TouchHotIron_Yell()
{
var hand = MockRepository.GenerateStub<IHand>();
var mouth = MockRepository.GenerateMock<IMouth>();
hand.Stub(h => h.TouchIron(null)).Constraints(Is.Anything()).Throw(new BurnException());
mouth.Expect(m => m.Yell());
var brain = new Brain(hand, mouth);
brain.TouchIron(new Iron { IsHot = true });
mouth.VerifyAllExpectations();
}
Yes, as NMock2, Rhino Mocks distinguishes between mocks and stubs. Not surprisingly, with mocks you record and verify expectations but stubs are just for replacing the behavior that is needed to fulfill a test.
Pros and cons.
Both strict and non-strict mocks are supported (GenerateMock creates the latter, as does "old fashioned" DynamicMock). Non-strict ones wouldn’t throw exceptions on unmet expectations.
And it can mock classes! So in the previous example we could have used Hand class instead of the interface. However, the framework can't mock non-virtual methods, so we'd need to make TouchIron method virtual. The reason for that lies deep inside: Rhino Mocks uses Castle
Dynamic Proxy to handle proxying the types it needs to mock, and Dynamic Proxy cannot intercept calls to non-virtual, non-abstract
methods.
Other limitations: cannot mock a sealed class and cannot mock a private interface.
And, of course, you cannot mock static methods. While it's true that static methods often imply bad design, let's talk about it later. Here we just mention that Rhino Mocks cannot mock static methods, because in future we'll see that there is a framework that can do that.
Actualy, it's s hard to find flaws in Rhino Mocks. Perhaps, error messages are a bit fuzzy: if mouth.Yell() has not been called, we’d see just
Rhino.Mocks.Exceptions.ExpectationViolationException: IMouth.Yell(); Expected #1, Actual #0.
It's somewhat less comprehensive than in NMock2. On the other hand,
Rhino Mocks provides better support for refactoring tools because it does not use string names to build the expectations[2].
Aerobatics.
Rhino Mocks is a pretty powerful framework and supports a lot of quite sophisticated scenarios. Those range from ordered mocking (test the call order) to recursive mocking (mock out a chain of calls) to accessing method arguments directly (instead of setting constraints or asserting on particular values).
I would recommend to use those sparingly. Your tests (especially, interaction based tests) should be as simple as possible: simplicity is indispensable for a robust test. That's why you need a very good excuse to give a test some knowledge about implementation details of the system.
Say, knowledge about calls order and internal interactions.
Such tests can become very powerful but, at the same time, very fragile. Any update on the implementation could break them. Sometimes it's actually what you want – but sometimes it's not. Anyway, the majority of the tests should just check the APIs: asserting results of the calls, ensuring we get exceptions in certain cases. As for the rare cases where you'd need something more specific... this can probably happen in legacy code if you are under pressure and cannot afford proper refactoring (in this case, testing code by any means is definitely better that not testing at all).
So, it's not like all those aerobatics testing features are bad, they're just a Holy Hand Grenade in your arsenal. A sledge-hammer that is not the best tool to crack nuts.
And yet another powerful (and somewhat confusing for newbies) feature: constraints. Their syntax includes 4 (four!) classes (Is, Property, List and Text) and the framework allows you to define custom ones. All aforementioned warnings are relevant here as well.
Sum up.
Pros:
1. Open source and free.
2. Type safe.
3. Very powerful.
4. Can mock classes.
5. Allows both strict and non-strict mocks.
Cons:
1. Poor internal error messages.
2. A bit cluttering API for constraints.
3. Cannot mock non-virtual, non-abstract and static methods, cannot mock sealed classes and private interfaces.
Footnotes.
[1] – If you happen to come across this post before reading the original story of touching a hot iron, you may want to read it over. And here is the code for the types used in the example.
[2] – We already learned that NMock2 can be extended to become type safe and refactoring friendly: just scroll down to "Improvements: NMock2 and C#3" section in this post.