Ayende @ Wiki


Planning 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:

  1. The project is a new project and the user cancels on new name.
  2. The project is a new project, the user gives a new name and the project is saved.
  3. The project is a new project, the user gives a name that already exists and does not approve an overwrite.
  4. The project is a new project, the user gives a name that already exists and approves an overwrite.
  5. 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).


The first test

public void SaveProjectAs_CanBeCanceled()
  MockRepository mocks = new MockRepository();
  IProjectView projectView = mocks.StrictMock<IProjectView>();
  Project prj = new Project("Example Project");
  IProjectPresenter presenter = new ProjectPresenter(prj,projectView);

We create a MockRepository, 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. It 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 reduces the chances of making a mistake that will only (hopefully) be caught at runtime - When tests are run.
  • We use explicit calls to move from Record state to Replay state.
  • We verify that all the expectations have been met.

This is about as simple example as can be had. The real test moves are: Create the MockRepository, the project, the view and presenter for the setup method (Since they are required 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.


Mocks, 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 StrictMock()
  • 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 means 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: All method calls during the replay state are accepted. If there is no special handling setup for a given method, a null or zero is returned. All of the expected methods must be called for the object 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 differences between the tests are marked with bold.
First, the mock object code:

[ExpectedException(typeof (ExpectationViolationException), 
    "IDemo.VoidNoArgs(); Expected #0, Actual #1.")]
public void MockObjectThrowsForUnexpectedCall()
  MockRepository mocks = new MockRepository();
  IDemo demo = mocks.StrictMock<IDemo>();
  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:

public void DyamicMockAcceptUnexpectedCall()
  MockRepository mocks = new MockRepository();
  IDemo demo = mocks.DynamicMock<IDemo>();
  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 (StrictMock) 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

ScrewTurn Wiki version 2.0 Beta. Some of the icons created by FamFamFam.