Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,954 | Comments: 44,412

filter by tags archive

Beautiful (nontrivial) Code - Rhino Mocks 3.5's AssertWasCalled


Beautiful code is not something that is easy to define. I think of this as something that is extremely elegant, that solve a hard problem in a way that isn't brute force. I think that the way Rhino Mocks implements the AssertWasCalled functionality is elegant, and I would like to point it out.

I know of at least one contributor to Rhino Mocks who consider that piece of code scary, by the way, so it is not cut & dry.

Here is the actual method call:

public static void AssertWasCalled<T>(this T mock, Action<T> action, 
	Action<IMethodOptions<object>> setupConstraints)
{
	ExpectationVerificationInformation verificationInformation = 
		GetExpectationsToVerify(mock, action, setupConstraints);

	foreach (var args in verificationInformation.ArgumentsForAllCalls)
	{
		if (verificationInformation.Expected.IsExpected(args))
		{
			verificationInformation.Expected.AddActualCall();
		}
	}
	if (verificationInformation.Expected.ExpectationSatisfied)
		return;
	throw new ExpectationViolationException(
		verificationInformation.Expected.BuildVerificationFailureMessage());
}

We will get the GetExpectaionsToVerify in a bit, but broadly, it gets the expectation that should have been called and then it execute the same logic that it would have in the Record/Replay model. In fact, it is an exact reversal of the Record/Replay model. Now we record all the actual calls, and then we create an expectation and try to match it against the actual calls that were made against the actual object.

Of even more interest is how we get the expectation that we are verifying:

private static ExpectationVerificationInformation GetExpectationsToVerify<T>(T mock, Action<T> action,
		Action<IMethodOptions<object>> setupConstraints)
{
	IMockedObject mockedObject = MockRepository.GetMockedObject(mock);
	MockRepository mocks = mockedObject.Repository;

	if (mocks.IsInReplayMode(mockedObject) == false)
	{
		throw new InvalidOperationException(
			"Cannot assert on an object that is not in replay mode." +
			" Did you forget to call ReplayAll() ?");
	}

	var mockToRecordExpectation = (T)mocks.DynamicMock(
		mockedObject.ImplementedTypes[0], 
		mockedObject.ConstructorArguments);

	action(mockToRecordExpectation);

	AssertExactlySingleExpectaton(mocks, mockToRecordExpectation);

	IMethodOptions<object> lastMethodCall = mocks.LastMethodCall<object>(mockToRecordExpectation);
	lastMethodCall.TentativeReturn();
	if (setupConstraints != null)
	{
		setupConstraints(lastMethodCall);
	}
	ExpectationsList expectationsToVerify = 
		mocks.Replayer.GetAllExpectationsForProxy(mockToRecordExpectation);
	if (expectationsToVerify.Count == 0)
	{
		throw new InvalidOperationException(
			"The expectation was removed from the waiting expectations list,"+
			" did you call Repeat.Any() ? This is not supported in AssertWasCalled()");
	}
	IExpectation expected = expectationsToVerify[0];
	ICollection<object[]> argumentsForAllCalls = mockedObject.GetCallArgumentsFor(expected.Method);
	return new ExpectationVerificationInformation
			{
				ArgumentsForAllCalls = new List<object[]>(argumentsForAllCalls),
				Expected = expected
			};
}

This is even more interesting. We create a new mocked object, and execute it in record mode against the expectation that we wish to verify. We gather this expectation and extract that from the newly created mock object, to pass it to the AssertWasCalled method, where we verify that against the actual calls made against the object.

What I find elegant in the whole thing is not just the reversal of the record / replay model, it is the use of Rhino Mocks to extend Rhino Mocks.


Comments

Frank Quednau

"Extending Rhino Mocks with Rhino Mocks" - No kidding, sometimes I look at some object implementation in a system and wonder why I bother with an implementation if I could instead use Rhino Mocks, declare what I want the instance to do and be done with it.

Jeremy Gray

While I'm sure it will take me a bit to get my head fully wrapped around everything that is going on in GetExpectationsToVerify, I do have a small comment about AssertWasCalled:

Law of Demeter violation alert!

You begin a minor violation at the foreach line, which gets much more serious when ".Expected." starts showing up repeatedly. If these things were factored into their proper locations (probably everything from the foreach on down first being moved into ExpectationVerificationInformation, and then all of the ".Expected." blocks moved into their appropriate types), with clear names assigned to each new member generated in the process, perhaps that Rhino.Mocks contributor, and others, would have an easier time building up an understanding of the code.

Neal Blomfield

The scary thing about this is it's getting to the point were I expect no less from you Oren. You're code repeatedly makes me look at how we can achieve what is required in different (and better) ways - you take thinking outside the box to a whole new level. Nice code, great way to solve the problem and I am looking forward to being able to use it if I ever get round to building some .NET 3.5 bits :)

Aaron Jensen

I think it's an ingenious approach for adding AssertWasCalled to a record replay framework without completely redoing the plumbing. I applaud you for that.

It's very complicated though and someone who is not totally familiar with the internals of Rhino.Mocks will find it scary. Scary doesn't mean bad, it just means that its not understandable at first glance. It requires some digging and who knows what you'll find.

Ayende Rahien

Jermey,

ExpectationVerificationInformation is a DTO, it contains just the expectation and the actual arguments made.

Jeremy Gray

@Oren - Ahh, that sheds a bit more light on things. Too bad, though, as I have often found that refactorings to clean up Demeter / Tell, Don't Ask violations end up introducing nice clear names for the actions being taken by the code.

In any case, thanks for taking the time to post up some of your favorite tidbits from within Rhino.Mocks. For folks like me who love using Rhino.Mocks but who haven't had an opportunity to dig into its code, the little bits of insight into its inner workings are greatly appreciated.

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

  1. What is new in RavenDB 3.5–Intro - 15 hours from now

There are posts all the way to Jul 08, 2015

RECENT SERIES

  1. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
  2. Special Offer (2):
    27 May 2015 - 29% discount for all our products
  3. RavenDB Sharding (3):
    22 May 2015 - Adding a new shard to an existing cluster, splitting the shard
  4. Challenge (45):
    28 Apr 2015 - What is the meaning of this change?
  5. Interview question (2):
    30 Mar 2015 - fix the index
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats