Order in the mocking!

time to read 3 min | 581 words

Consider this little tidbit:

MockRepository mocks = new MockRepository();
ICustomer customer = mocks.CreateMock<ICustomer>();

using (mocks.Record())
using (mocks.Ordered())
{
	Expect.Call(customer.Id).Return(0);

	customer.IsPreferred = true;
}

using (mocks.Playback())
{
	customer.IsPreferred = true;
}

What would you expect would be the result of this code? Well, it should obviously fail, right? And probably give a message to the effect of unordered method call. Well, it does fail, but it gives the usual message, not talking about ordered method calls at all.

The error message is misleading, what was going on?

I wonder if I am going to bore someone with this description, but what the hell, you can always skip ahead.

Rhino Mocks records all expectations in a recorder. This recorder can be nested. Rhino Mocks has two of those, the Unordered Recorder and the Ordered Recorder. The ability to nest recorders is fairly interesting part of Rhino Mocks, and was a lot of fun to write correctly. It also means that you have a lot of freedom in how you write the tests, because you can specify with great granularity how things are working. (Grouping methods that should run together, for instance.)

But, Rhino Mocks only read from one recorder at a time. Let us talk about a concrete example, shall we? In the above code, here is the recorder structure that we have set up:

image Now, why is this important?

Let us consider how Rhino Mocks will try to resolve a method call.

First, Rhino Mocks will ask the root recorder if it has an expectation that match the method call. The recorder then checks against the recordedActions list.

The recorded actions list contains both normal expectations and nested recorders. When a recorder encounters a nested recorder, it enquire whatever this expectation is valid for the recorder. The nested recorder can say that it isn't, at which point the parent recorder needs to decide what to do. In the unordered recorder case, it will simple move to the next expectation or recorder, but in the ordered recorder case, it will throw an exception because of the unordered method

Here is where it starts to get interesting. Let us look at the playback part of the code above. We call customer.IsPreffered(true);

Rhino Mocks then asks the root recorder whatever this is a valid expectation. The root recorder scans its recorded actions list, which means that it finds the ordered recorder, and then it enquire whatever this is an expected operation.

Well, is customer.set_IsPreffered an expected method call?

No, it is not. The expected (ordered) method call for the ordered recorder is the customer.Id call, so the ordered recorder will answer that this is not an expected call. The root recorder now proceeds to check the rest of the recorded actions list, which is empty, so it finds that we have gotten a completely unknown method, and therefor it throws an exception. But since it is the default, unordered, recorder that throws, we get the unordered version of the exception, not the ordered one, which we have probably been expecting to see.

This is unexpected behavior, but it is precisely how it should behave.

Nevertheless, it violates the rule of least surprise, and this entire post is now mostly irrelevant, because I have just committed a change to Rhino Mocks that ensures that recorders that only contains a single nested recorder are merged with that recorder, which makes it behave in a way that is more in line with the expected behavior.