Rhino Mocks, or "How to Explain the Introductory Chapter of a Mock Framework"
There's a good chance I'll lose some credibility with this post and that many people will file it under "stating the obvious". But a wise man once told me, "There aren't any stupid questions, are there?" so I'll proceed assuming someone has had the same trouble I did when I was learning mock objects, and more specifically, Rhino Mocks (or RhinoMocks or Rhino.Mocks depending on who you talk to).
My main conceptual stumbling block was in the use of the term "expectation". To explain, here is the introductory example from the Rhino Mocks documentation:
1 [Test] 2 public void SaveProjectAs_CanBeCanceled() 3 { 4 MockRepository mocks = new MocksRepository(); 5 IProjectView projectView = (IProjectView)mocks.CreateMock(typeof(IProjectView)); 6 Project prj = new Project("Example Project"); 7 IProjectPresenter presenter = new ProjectPresenter(prj,projectView); 8 Expect.Call(projectView.Title).Return(prj.Name); 9 Expect.Call(projectView.Ask(question,answer)).Return(null); 10 mocks.ReplayAll(); 11 Assert.IsFalse(presenter.SaveProjectAs()); 12 mocks.VerifyAll(); 13 }
(Note: The example is a little dated due to Rhino Mocks' new CreateMock<T> but that wasn't the confusing part. Nor was the typo in line 4. Read on, reader, read on.)
I read/scanned the surrounding text several times trying to determine where the magic was happening. Focussing on line 8, here was my interpretation:
- At some point during this test, there will be a call to projectView.Title. When that happens, I expect it's value to be identical to prj.Name. If it isn't, the test fails.
That is, I thought the expectation itself was doing the testing. Ya, ya, stop laughing. Sue me for assuming the English definition of "expect". (OK, so the expectation is on the .Call, not the .Return; I'm using Reverse Polish Notation, SOP in the Coding Hillbilly handbook.)
What line 8 *really* says is (to the best of my knowledge at least):
- At some point during the test, I expect a call will be made to projectView.Title. When that happens, return prj.Name as its value. If the call isn't made, the test fails.
The good part is, realizing this made ReplayAll and VerifyAll that much less confusing particularly around the Replay State that the documentation assumes I know anything about.
It took a while for me to understand the Replay State. As far as I can tell, MockRepository.ReplayAll turns on a switch. When that switch is on, all calls to methods/properties on the mock objects are recorded and compared to the expectations (set before ReplayAll is called). Then when VerifyAll is called, the switch is turned off and we check to see if all expectations have been met.
Put another way:
- Create your mock objects
- Tell the mock framework which properties/methods you expect will be called. Along with this, you can specify which values you want to return when those methods/properties are called.
- Start recording (see line 10). For all code executed after this, start comparing it with our expectations.
- Execute code to be tested
- Stop recording (see line 12). See which expectations have not been met and fail the test if any exist.
So again, the test will fail if you set an expectation that a method will be called and it isn't. I believe the reverse is true as well. That is, if a method is called and you *haven't* set an expectation on it, the test will fail also. But don't quote me on that.
I'm sure all of this is being explained much more clearly as I type this at the Calgary Code Camp at either James Kovacs' or Terry Thibodeau's sessions but I'm posting it anyway to justify the time spent ignoring my daughter.