EditPlanning the tests
[I tried to create an example for this article, but the example was both contrived and didn't portray the possibilities correctly. So most of the code samples here are taken from NHibernate Query Analyzer tests and are "real code".]
The purpose of mock objects is to allow you to test the interactions between components, this is very useful when you test code that doesn't lend itself easily to state based testing. Most of the examples here are tests that check the save routine for a view to see if it is working as expected. The requirements for this method are:
- If the project is a new project, then ask for a name for the project. (Allow canceling save at this point )
- If the new project name already exists, ask if user wants to overwrite it. (If not, cancel the save operation)
- If the user approves the overwrite, delete the old project.
- Save the project.
These requirements give us five scenarios to test:
- The project is a new project and the user cancels on new name.
- The project is a new project, the user gives a new name and the project is saved.
- The project is a new project, the user gives a name that already exists and does not approve an overwrite.
- The project is a new project, the user gives a name that already exists and approves an overwrite.
- The project already exists, so just save it.
Trying to test the above using state based testing will be awkward and painful, using interaction based testing, it would be a breeze (other types of scenarios might be just as painful to test using interaction based testing but easier to test with state based testing).
EditThe first test
[Test]
public void SaveProjectAs_CanBeCanceled()
{
MockRepository mocks = new MockRepository();
IProjectView projectView = mocks.CreateMock<IProjectView>();
Project prj = new Project("Example Project");
IProjectPresenter presenter = new ProjectPresenter(prj,projectView);
Expect.Call(projectView.Title).Return(prj.Name);
Expect.Call(projectView.Ask(question,answer)).Return(null);
mocks.ReplayAll();
Assert.IsFalse(presenter.SaveProjectAs());
mocks.VerifyAll();
}
We create a MockRepository and create a mock project view, project and a project presenter, then we set the expectations. After we finished with setting up the expectations, we move to the replay state, and call the SaveProjectAs() method, which should return false if the user canceled the save process.
This example clarifies several key concepts related to Rhino Mocks.
- We set expectation on object using the object's methods, and not strings, this reduce the chances of making a mistake that will only (hopefully) be caught at runtime, when you run the tests.
- We are using explicit call to move from Record state to Replay state.
- We verify that all the expectations has been met.
This is about as simple example as can be had, the real test moves creating the MockRepository, the project, the view and presenter to the setup method, since they are require for each test, and the expectations verification to the teardown method, since it's easy to forget that. You can see that we expected two methods to be called, with specific arguments, and that we set a result for each. This method uses parameter matching expectations, Rhino Mocks supports several more. More info: Rhino.Mocks::Method Calls.
EditMocks, Dynamic Mocks and Partial Mocks oh my!:
Rhino Mocks currently support the creation of the following types of mock objects.
- Mock Objects - Strict replay semantics. - Created by calling CreateMock()
- Dynamic Mock - Loose replay semantics. - Created by calling DynamicMock()
- Partial Mock - Mock only requested methods. - Created by calling PartialMock()
What is the meaning of these?
Strict replay semantics: only the methods that were explicitly recorded are accepted as valid. This mean that any call that is not expected would cause an exception and fail the test. All the expected methods must be called if the object is to pass verification.
Loose replay semantics: any method call during the replay state is accepted and if there is no special handling setup for this method a null or zero is returned. All the expected methods must be called if the object is to pass verification.
Mocking only requested methods: this is available for classes only. It basically means that any non abstract method call will use the original method (no mocking) instead of relying on Rhino Mocks' expectations. You can selectively decide which methods should be mocked.
Let's see some code that would explain it better than words. The difference between the tests are marked with bold. First, the mock object code:
[Test]
[ExpectedException(typeof (ExpectationViolationException),
"IDemo.VoidNoArgs(); Expected #0, Actual #1.")]
public void MockObjectThrowsForUnexpectedCall()
{
MockRepository mocks = new MockRepository();
IDemo demo = mocks.CreateMock<IDemo>();
mocks.ReplayAll();
demo.VoidNoArgs();
mocks.VerifyAll();//will never get here
}
As you can see, calling a method that wasn't explicitly setup will cause an exception to be thrown, now let's try it with a dynamic mock:
[Test]
public void DyamicMockAcceptUnexpectedCall()
{
MockRepository mocks = new MockRepository();
IDemo demo = mocks.DynamicMock<IDemo>();
mocks.ReplayAll();
demo.VoidNoArgs();
mocks.VerifyAll();//works like a charm
}
An unexpected call is ignored when you are using a dynamic mock. However, this is the only difference between the two types, in all other ways they are identical (ordering, recording a method expectation, callbacks, etc). Any expectations that were created during the record phase must be satisfied if the dynamic mock is to pass verification.
Dynamic mocks are useful when you want to check a small piece of functionality and you don't care about whatever else may happen to the object. Mock objects are useful when you want complete control over what happens to the mocked object.
For an explanation of partial mocks, see
Rhino Mocks Partial Mocks
Up:
Rhino Mocks Documentation
Next:
Rhino Mocks Generics