In my current project I'm trying to apply the best practices for building "great software" (cit.).

Beware the Rhino! by metal-dogOne of the best practices is using a (kind of) TDD approach, using Inversion of Control and Dependency Injection to test only the relevant parts of your code without testing all the layers of your applications, DB included, during each test.

Which are the benefits of this approach?

  • First of all your tests run quicker because they are not hitting the database all the times.
  • Second, you don't need to restore the initial state for the database.
  • Third, if you are working in a team with many developers, the ones that are working on the presentation layer can test their code even if the business layer or the database side of the application is not working yet.
  • Last, if your application is accessing 3rd party webservices you don't your tests to fail because of a network problem or, as it happened to me last week, if the database server dies unexpectedly.

Rhino Mocks

I decided to Ayende's Rhino Mocks to mock my layers.

At the moment I'm still in an early phase of the development, so my problem was mocking the database access layer, which is developed using a very trimmed down version of the IRepository<T> that is inside Rhino Commons.

IRepository<T> is a class that wraps NHibernate and provide a generic interface to its ISession methods.

Now let's see how to to test the Persistence Service that retrieves an User calling the IRepository<User>.

How to Mock the IRepository<T> and the Get(object) method

First I want to test the GetById method. Here a simple implementation of it:

   1:  public User GetById(int id)
   2:  {
   3:    User returnUser = _repository.Get(id);
   4:    if (returnUser == null)
   5:      {
   6:        returnUser = new UnknownUser();
   7:      }
   8:    return returnUser;
   9:  }

The Get method then calls the NH Session Get method.

And here is test that uses Rhino Mock to simulate the real IRepository<User>.

   1:  [Test]
   2:  public void CanGetUserById()
   3:  {
   4:    MockRepository mocks = new MockRepository();
   5:    IRepository<User> mockedRepository = 
   6:      mocks.CreateMock<IRepository<User>>();
   7:    User expectedUser = new User();
   8:    expectedUser.EmailAddress = "email@address.com";
   9:    expectedUser.Name = "Test User";
  10:    Expect.Call(mockedRepository.Get(666)).Return(expectedUser);
  11:    mocks.ReplayAll();
  12:    UserPersistenceService service =
  13:      new UserPersistenceService(mockedRepository);
  14:    User user = service.GetUserById(666);
  15:    Assert.AreEqual("email@address.com", user.EmailAddress);
  16:  }

Line 4: I create a new MockRepository to store all the mock object I will create and the expectations I will set on them.

Lines 5,6: I create a Mock object to mimic the behavior of our real  IRepository<User>.

Lines 7-9: I build the object what I want the mock object to return when I call the method.

Line 10: This is the most important line of the snippet. It instructs the Mock object to "Expect" a "Call" to the Get method on the mocked repository with an parameter of value 666 and, when this happens, to "Return" the object expectedUser.

Line 11: "starts" the mocked repository.

Lines 12,13: The mocked repository is "injected" inside the persistence service.

Line 14: I call the GetUserById method on the persistence service with 666 as parameter, this method calls the Get method of the repository (that is our mocked repository) which returns immediately the expectedUser without calling the real repository and hitting the DB.

Line 15: Now I can test the equality between the username of the user retrieved from the DB repository and what I expected to find.

In another post I'll write about mocking another methods of the repository, a method that accepts an array of NH Expressions. And I'll show how to extend Rhino Mock to "understand" this new type of parameter.

UPDATE: Small edit to the code of the samples after 2 suggestions from Simone Busoli.

Technorati tags: , , , ,

kick it on DotNetKicks.com