Tomer Gabel's annoying spot on the 'net RSS 2.0
# Monday, July 9, 2007

I've been working with Rhino Mocks for a couple of days now and I'm duly impressed. Aside from being generally useful, the Rhino Mocks syntax is mostly natural and some very clever design hacks are used to make it work the way you'd expect it to. (In case you don't know what mocking frameworks are, the subject has been tackled ad nauseam. Just take your pick.)

There is one caveat that is not immediately visible in the Record-Playback syntax, though:

internal interface IFoo
    int Bar();
[ExpectedException( typeof( InvalidOperationException ),
    "Type 'System.Object' doesn't match the return type " +
"'System.Int32' for method 'IFoo.Bar();'" )] public void TestRecordSyntax_ReturnTypeMismatchBehavior() {
MockRepository mocks = new MockRepository(); IFoo mock = mocks.CreateMock<IFoo>(); using ( mocks.Record() ) Expect.Call( mock.Bar() ).Return( new object() ); }

This simple test merely returns an incorrectly typed return value for the IFoo.Bar method, so with the ExpectedException in place you'd expect it to succeed, but you're in for a surprise:

[failure] RecordPlaybackTests.TestSetup.ConsistentReturnTypeMismatchBehavior
TestCase 'RecordPlaybackTests.TestSetup.ConsistentReturnTypeMismatchBehavior'
failed: Exception of type 'MbUnit.Core.Exceptions.ExceptionExpectedMessageMismatchException' was thrown. Expected exception message: "Type 'System.Object' doesn't match the return type 'System.Int32' for method 'IFoo.Bar();'", got "Previous method 'IFoo.Bar();' require a return value or an exception to throw.".

To understand this, consider the following flow:

  1. A method call expectation is set (by calling mock.Bar())
  2. Attempting to set a wrong return type results in the expected exception being thrown from Rhino Mocks. This happens before the Return constraint is set
  3. Because using calls IDisposable.Dispose in the finally block, the mocks.Record object calls mocks.ReplayAll()
  4. ReplayAll asserts because a required expectation (return value) is not yet set, masking the expected exception!

This is in fact how I first encountered this behavior, however there are several scenarios where this can happen. One example: setting up a return value that throws an exception on construction, possibly due to invalid arguments, causes the same behavior thereby masking the true cause of the error.

An alternative syntax suggested by Ayende (author of Rhino Mocks) is:

    With.Mocks( delegate
                    Expect.Call( mock.Bar() ).Return( new object() );
                } ); 

Aside from the esthetical aspect (i.e. butt-ugly), this syntax introduces a minor issue: you cannot Edit-and-Continue methods with anonymous delegates. This may not be a big deal for test code but it's worth noting.

All in all I don't consider this a big issue, but it can bite and should therefore be understood and documented. This post will be reflected in the Rhino Mocks wiki shortly and will hopefully save someone a little grief in the future...

Monday, July 9, 2007 7:53:40 AM (Jerusalem Standard Time, UTC+02:00)  #    -
Send mail to the author(s) Be afraid.
<September 2023>
All Content © 2023, Tomer Gabel
Based on the Business theme for dasBlog created by Christoph De Baene (delarou)