Occasionally on the RhinoMocks mailing list a question pops up about various pieces of the framework that people struggle with. This post is the first in hopefully an ongoing series where I address specific underpinnings of RhinoMocks to you, the reader. I’ll do my best to explain the inner workings of the code and what is going on under the covers while trying to keep it “real”. If you have specific things you want to see, let me know and I will do my best to put together the tutorial.
That said, let’s get started…
Occasionally a question will come in about RhinoMocks caching a value and that something must be a bug in the framework. Consider the following very simple test where we want to return the current date/time from a property getter. You have a component with a gettable time but no setter. In order to fake this out we use the Stub() method return a "known" value.
1: [TestFixture]
2: public class TimTests
3: {
4: [Test]
5: public void DateTimeDive()
6: {
7: var stub = MockRepository.GenerateStub<IFoo>();
8:
9: stub.Stub(x => x.Current).Return(DateTime.Now);
10:
11: Thread.Sleep(5000);
12:
13: Console.WriteLine(stub.Current);
14: }
15: }
16:
17: public interface IFoo
18: {
19: DateTime Current { get; }
20: }
What time do you suppose is written out the console given the Thread.Sleep cal? The common thinking is that the Stub() method stores a method to be called to return the value at the time of execution when in reality what is happening is that the stub.Stub(x => x.Current).Return(DateTime.Now) line is evaluated at runtime at the time the Stub is set. Therefore, in the above code, the Console.WriteLine(stub.Current) will actually write the time from 5 seconds (5000 milliseconds prior) when the stub call was made. In other words, when Stub() is called, the resulting value is calculated and ready to be returned, it's not deferred for execution.
What is going on is that deep in the bowels of RhinoMocks there are things called Expectations, MockStates, and Recorders. In the code above, when Stub() is called, what happens is a two step process (but appears as one, given the fluency, or dot-dot notation, of the call).
1. An expectation is created with a null return value (stub.Stub(x=>x.Current))
2. The expectation is updated with the known return value (.Return(DateTime.Now))
(Note that if you try to write code without the second piece, you will get an InvalidOperationException when you try to use the stubbed property stating:
failed: Method 'IFoo.get_Current();' requires a return value or an exception to throw.
System.InvalidOperationException: Method 'IFoo.get_Current();' requires a return value or an exception to throw.
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks\Expectations\AbstractExpectation.cs(342,0): at Rhino.Mocks.Expectations.AbstractExpectation.ReturnOrThrow(IInvocation invocation, Object[] args)
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks\Impl\StubReplayMockState.cs(63,0): at Rhino.Mocks.Impl.StubReplayMockState.DoMethodCall(IInvocation invocation, MethodInfo method, Object[] args)
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks\Impl\ReplayMockState.cs(118,0): at Rhino.Mocks.Impl.ReplayMockState.MethodCall(IInvocation invocation, MethodInfo method, Object[] args)
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks\MockRepository.cs(678,0): at Rhino.Mocks.MockRepository.MethodCall(IInvocation invocation, Object proxy, MethodInfo method, Object[] args)
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks\Impl\RhinoInterceptor.cs(96,0): at Rhino.Mocks.Impl.RhinoInterceptor.Intercept(IInvocation invocation)
c:\OSS\Castle\Tools\Castle.DynamicProxy2\Castle.DynamicProxy\AbstractInvocation.cs(202,0): at Castle.DynamicProxy.AbstractInvocation.Proceed()
at IFooProxybc98a6ed8f1b4aa3b9ea36d89f80e6af.get_Current()
C:\dev\OSS\rhino-tools\mocks\Rhino.Mocks.Tests\TimTests.cs(43,0): at Rhino.Mocks.Tests.TimTests.err()
There is an "expectation" created when the call is made and that expectation is added to a recorder (There are ordered and unordered method recorders, by default (the most common scenario) you will be using unordered recorders). These recorders record the mocked object (our stub object here), the method being called, and the "expectation". The call to .Return(DateTime.Now) updates the expectation with the desired return value. In the RhinoMocks framework this code is very simple but it's important to note this value is executed at the point this code is run.
1: public IMethodOptions<T> Return(T objToReturn)
2: {
3: expectation.ReturnValue = objToReturn;
4: return this;
5: }
You can see from the code above that setting up a return value simply updated the expectation's return value.
I hope you've followed the above code. The RhinoMocks codebase is not simple per se and you do not need to understand it in depth to use it but I think in this case it help (as I see it often pop up on the mailing list).
So what do you do if you want something to be called in a delayed fashion? In our example I want the call to IFoo.Current to represent the DateTime.Now when I call it (as opposed to when I set it up). To do that, simply use the "Do()" method. Here is an example that provides the same functionality as above, only the return value of the Stub is deferred until the stub is actually called.
1: [Test]
2: public void Realtime_results_with_Do()
3: {
4: var stub = MockRepository.GenerateStub<IFoo>();
5:
6: stub.Stub(x => x.Current).Do(new Func<DateTime>(() => DateTime.Now));
7:
8: Thread.Sleep(5000);
9:
10: Console.WriteLine(stub.Current);
11: }
I hope this helps a bit with the confusion around RhinoMocks and perceived "caching" of values. If you have questions about this or other areas of RhinoMocks, I would love to tear into them for you, just ask!
Posted
08-21-2009 5:26 PM
by
Tim Barcz