Rhino Mocks Callbacks

A callback is a user supplied delegate that is called whenever Rhino Mocks needs to evaluate whether a method call is expected or not. This is useful in some scenarios when you want to do a complex validation on a method arguments, or have a complex interactions between objects that you need to mock in the tests. I added this feature because I want to test some threading code which had the semantics of: Start the Job, and notify me when it's done. The only way to re-create that without bringing the real thread (and killing test isolations) was to plug my own code in during the replay state and call the tested object myself.

Some things to consider before you decide to use callbacks:

Your callback delegate must have a bool return type and it must return true if the method passed your validation. If it didn't pass you validation, just return false and Rhino Mocks will take it from there. The return value signals to the framework that this is the method call that this callback should match.

It can be abused very easily. It can be very hard to understand tests that use callbacks, because you get calls from supposedly innocent code. Do it only if you need it.

You callback may (and likely will be) called several times; keep that in mind when you write it. Either wrap it all in a if ( firstTimeCalled ) { /*do work*/ } or make sure that repeated calls for the delegate won't have some nasty side effects.

If it is so open to abuse, why add it?

Because when you need it, you really need it, and I would prefer that I'd some nice way to do it, and not some really ugly hacks.

Because I respect that those who will use this framework not to take the power and abuse it.

The technical details - In order to be a valid callback, the callback must return a Boolean, and have the same arguments as the mocked methods. You register a delegate using the following code:

Recursive Expectations

One final word of warning regarding callbacks. If your callback will initiate an action that cause another call on a mocked object, this will fail when mixed with Ordered(). The reason for that is that the framework cannot decide whether to accept the call or not, and calling another mocked method while evaluating means that the currently expected call is the one still being evaluated. This will fail the test. Using Unordered(), it will work, since the second expectation is not dependant on the first one being accepted first. In short, Ordered() is not re-entrant :-)

Usage with Other MethodOptions

Note that RhinoMocks will throw a InvalidOperationException at runtime if you attempt to specify both a callback AND a constraint. Rather than have possibly conflicting criteria in two places, check for parameter constraints in the callback if you're using one.