Comparing expectations with Constraints using Rhino Mocks

I've been more work with Rhino lately (and really digging it) and wanted to pass on a tidbit that might be useful to some of you.

Here's the setup. You're implementing the MVP pattern and your view will contain a list of items (probably displayed in a grid, listbox, etc.). You start with a test where your presenter will initialize the view with a list of items from a backend system. You want your test to verify that the view gets updated (via the presenter) with the correct list.

Let's setup our test using mocks. We'll mock out the view since the implementation isn't important to us right now. We only want to ensure that when the presenter is initialized (via a called to OnViewReady) it will 

  1. Call a backend system to get a list of items
  2. Set the view with those list of items
  3. We can ensure the view contains those items.

Here's the test for that with excessive comments so it should be self explanatory:

   24 [Test]

   25 public void OnViewReady_ViewShouldContainItemsFromRepository()

   26 {

   27     // Create the mock objects

   28     MockRepository mocks = new MockRepository();

   29     IProjectListView listView = mocks.CreateMock<IProjectListView>();

   30 

   31     // Creat a real presenter and assign the mock view to it

   32     ProjectListViewPresenter listViewPresenter = new ProjectListViewPresenter();

   33     listViewPresenter.View = listView;

   34 

   35     // Setup our expectations for the view by getting

   36     // a list of projects from a repository (created via a factory)

   37     RepositoryFactory factory = new RepositoryFactory();

   38     ProjectRepository repository = (ProjectRepository)factory.GetRepository<Project>();

   39     List<Project> fakeProjectList;

   40     fakeProjectList = repository.GetAll();

   41     listView.Projects = fakeProjectList;

   42     LastCall.IgnoreArguments();

   43     Expect.Call(listView.Projects).Return(fakeProjectList);

   44 

   45     // Invoke the mock engine

   46     mocks.ReplayAll();

   47 

   48     // Call the real presenter that we want to test

   49     // This will call the repository to retrieve a list of

   50     // projects and update the view to include them

   51     listViewPresenter.OnViewReady();

   52 

   53     // Check the view to ensure it's been updated

   54     // with the list of projects from the repository

   55     Assert.AreEqual(3, listView.Projects.Count);

   56 

   57     // Teardown the mocks

   58     mocks.VerifyAll();

   59 }

Other than the setup code (creating the mocks, presenter, etc.) which could all be done once in a Setup method there's a few problems with checking the list of projects from the view. While we did mock out the view, we're checking the count only. If we want to ensure that the list contains the projects we expect we might have to iterate through the list and compare the Project objects that are returned and that's a lot of work.

There's an easier way to check lists with Rhino which measures up to the same amount of code but is much more effective at testing the contents of the list. We can use constraints on the expected call. Constraints is a list of constraint objects attached to your mock that tell you there are expectations that should match up to the constraint you define.

Instead of assigning our fake project list to our view then setting the expectation via an Expect.Call method, we'll add a constraint to the mock. We don't need to assign the listView.Projects value so we can set it to null and the next line, LastCall.Constraints(...) will be our contraints we want to attach to that mock item:

   30 listView.Projects = null;

   31 LastCall.Constraints(new AbstractConstraint[] { List.Equal(fakeProjectList) });

We still have to create the expectation, but now we use List.Equal(...) and pass in the fakeProjectList. This means when the mock runs it will ensure that the listView.Projects property not only contains the same number of items (like our original test) but the list is actually of the same type and is the same list (I think Rhino internally uses the Equals method on each item to compare them). So now our test looks like this:

   14 [Test]

   15 public void OnViewReadyM_ViewShouldContainItemsFromRepository()

   16 {

   17     // Create the mock objects

   18     MockRepository mocks = new MockRepository();

   19     IProjectListView listView = mocks.CreateMock<IProjectListView>();

   20 

   21     // Creat a real presenter and assign the mock view to it

   22     ProjectListViewPresenter listViewPresenter = new ProjectListViewPresenter();

   23     listViewPresenter.View = listView;

   24 

   25     // Set the expectations of the mock via a constraint

   26     RepositoryFactory factory = new RepositoryFactory();

   27     ProjectRepository repository = (ProjectRepository)factory.GetRepository<Project>();

   28     List<Project> fakeProjectList;

   29     fakeProjectList = repository.GetAll();

   30     listView.Projects = null;

   31     LastCall.Constraints(new AbstractConstraint[] { List.Equal(fakeProjectList) });

   32 

   33     // Invoke the mock engine

   34     mocks.ReplayAll();

   35 

   36     // Call the real presenter that we want to test

   37     // This will call the repository to retrieve a list of

   38     // projects and update the view to include them

   39     listViewPresenter.OnViewReady();

   40 

   41     // Teardown the mocks

   42     mocks.VerifyAll();

   43 }

Note that we also don't have to do the Assert.AreEqual check at the end. Our call to VerifyAll will handle that when the mock is torn down.

Like I said, the amount of code isn't that much different, you're just using a different call on setting up the mock. However you can add as many constraints as you like. For example you can ensure the list is of the right type, it contains that many items, and you can even tell it the first item has to have some pre-determined value. Constraints are stacked up and evaluated when you tear the mock down so make sure you put in the VerifyAll call.

This is just an example of changing the way you might check a list of items using constraints but you can use constraints for any type. For example if you had a single property on a mocked object you can add a constraint that it was greater than or equal to a certain value, or that it met a certain type if you were say testing a sub-class. Check out the "Is" class to do things like Is.GreaterThan, Is.LessThanOrEqual, Is.Type, etc.

Lots of possibilities here so dig in and play with constraints on your mocks. You might find something interesting and learn something new along the way!

kick it on DotNetKicks.com

7 Comments

  • Just to note, I prefer this syntax (no explicit array declaration):
    LastCall.Constraints(List.Equal(fakeProjectList));

    Also, the constraint that you specified is the default mode for Rhino Mocks. If you don't do anything, it will expect to get the same values back.
    In other words:

    view.Projects = fakeProjectsList;

    And:

    view.Projects = null;
    LastCall.Constraints(Is.Equals(fakeProjectsList));

    Are the same.
    Note that List.Equals compares the _content_ of the lists, and not the actual list instance.

  • Oren,

    I tried a test with just
    mockView.Projects = fakeProjectList;
    but got this stack error:

    TestCase 'Bantrel.Applications.EHT.Tests.Unit.Presenters.ProjectListPresenterFixture.OnViewReady_ViewShouldContainItemsFromRepository'
    failed: Rhino.Mocks.Exceptions.ExpectationViolationException : IProjectListView.set_Projects(System.Collections.Generic.List`1[Bantrel.Applications.EHT.Core.Project]); Expected #0, Actual #1.
    IProjectListView.set_Projects(System.Collections.Generic.List`1[Bantrel.Applications.EHT.Core.Project]); Expected #1, Actual #0.
    TearDown : System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
    ----> Rhino.Mocks.Exceptions.ExpectationViolationException : IProjectListView.set_Projects(System.Collections.Generic.List`1[Bantrel.Applications.EHT.Core.Project]); Expected #0, Actual #1.
    IProjectListView.set_Projects(System.Collections.Generic.List`1[Bantrel.Applications.EHT.Core.Project]); Expected #1, Actual #0.
    at Rhino.Mocks.MethodRecorders.UnorderedMethodRecorder.DoGetRecordedExpectation(Object proxy, MethodInfo method, Object[] args)
    at Rhino.Mocks.MethodRecorders.MethodRecorderBase.GetRecordedExpectation(Object proxy, MethodInfo method, Object[] args)
    at Rhino.Mocks.Impl.ReplayMockState.DoMethodCall(IInvocation invocation, MethodInfo method, Object[] args)
    at Rhino.Mocks.Impl.ReplayMockState.MethodCall(IInvocation invocation, MethodInfo method, Object[] args)
    at Rhino.Mocks.MockRepository.MethodCall(IInvocation invocation, Object proxy, MethodInfo method, Object[] args)
    at Rhino.Mocks.Impl.RhinoInterceptor.Intercept(IInvocation invocation, Object[] args)
    at ProxyInterfaceSystemSystemObject_Bantrel_Applications_EHT_AdminIProjectListView_Rhino_Mocks_InterfacesIMockedObject_System_Runtime_SerializationISerializable.set_Projects(List`1 value)
    C:\Projects\bantrel\EHT\EHT\Modules\Admin\Admin\Views\ProjectListView\ProjectListViewPresenter.cs(16,0): at Bantrel.Applications.EHT.Admin.ProjectListViewPresenter.OnViewReady()
    C:\Projects\bantrel\EHT\EHT\Source\Tests\UnitTests\Presenters\ProjectListPresenterFixture.cs(44,0): at Bantrel.Applications.EHT.Tests.Unit.Presenters.ProjectListPresenterFixture.OnViewReady_ViewShouldContainItemsFromRepository()
    --TearDown
    at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
    at NUnit.Core.TestMethod.doTearDown(TestCaseResult testResult)
    --TargetInvocationException
    at Rhino.Mocks.Impl.ReplayMockState.Verify()
    at Rhino.Mocks.MockRepository.Verify(Object obj)
    at Rhino.Mocks.MockRepository.VerifyAll()
    C:\Projects\bantrel\EHT\EHT\Source\Tests\UnitTests\Presenters\ProjectListPresenterFixture.cs(35,0): at Bantrel.Applications.EHT.Tests.Unit.Presenters.ProjectListPresenterFixture.TearDownMocks()

    That's why the constraints are setup the way they are. If I just comparing individual items (say 1 project, not a list of them) then it works fine. However in order to have the list test pass, I need to setup the constraint.

  • In addition to the List constraint I also like the And, and Or composite constraints that, conceivably, allow one to test more than equality. For example, if I want to test that a parameter value is between an upper and lower bound I can write: LastCall.Constraints(new And(Is.GreaterThanOrEqual(2), Is.LessThanOrEqual(10)));

  • Then it is not the same instance of the list, and your way is the way to go, yes.

  • Ahh, that makes sense. Yes, it's a different instance as the repositories are not singletons. We probably need to change that to inject the repository into the presenter which would fix the problem. Thanks.

  • I like your vs settings for editor colours. Can you post a link somewhere to share your file? I'm too lazy to try duplicate it myself. HaHaHa Thanks!

  • Bil,

    If you happen to have experience of other mocking tools, could you compare them?

Comments have been disabled for this content.