Creating a Web Application Using MVC, Unity and NHibernate – Part 3: Mocking

Friday, January 30, 2009

This is the third post in the series.  You can see the other to posts here:

The Service Layer

Now that I have a data layer, I would like to create a service layer that takes the records retrieved from the database and then applies the business rules to them.  Some of these rules are that I would like to apply are:

  • I would only like to show the first 5 most recent posts on the home page.
  • I would like catch any exceptions and log them.

If I was not testing this function first, my code would look something like this.

   37         public List<NewsItemDto> GetLatestNewsItems(int numberOfItems)

   38         {

   39             List<NewsItemDto> items;

   40 

   41             try

   42             {

   43                 using (ISessionFactory sessionFactory = (new Configuration().Configure().BuildSessionFactory()))

   44                 {

   45 

   46                     using (ISession session = new object() as ISession)

   47                     {

   48                         var provider = new NHibernateDataProvider(session);

   49                         var result = provider.GetAllPublishedFrontPagePosts();

   50                         items = result != null ? result.OrderByDescending(i => i.DatePublished).Take(5).ToList() : null;

   51                     }

   52                 }

   53             }

   54             catch (Exception ex)

   55             {

   56                 ExceptionPolicy.HandleException(ex, "General");

   57                 throw;

   58             }

   59 

   60             return items;

   61 

   62         }

 

Here are some of the issues with this method.

  • The method is depending on the NHibernateDataProvider class.  This means I have to have a database setup for this test.  If I have a lot of service layer tests connecting to the database then these tests are going to take forever to run.
  • Another issue with being dependant on the NHibernateDataProvider class is now I have to some way to create an ISessionFactory class.
  • For logging errors, I have a dependency on using Microsoft Exception Handler class so if I ever want to change that logging plumbing I have to change it all over my app.

·          In general there is a lot of stuff happening but really all I want to test is getting a list of news items that match the count I want and are sorted in the correct order.

The Test

Okay, so back to the drawing board.  Let me start with the test first and see if I can test this function without having it connect to a database. To do this I am going extract the interface for the NHibernateDataProvider class and call it IDataProvider.

    7     public interface IDataProvider

    8     {

    9         IQueryable<NewsItemDto> GetAllPublishedFrontPagePosts();

   10         NewsItemDto GetNewsItemByItemId(long newsItemId);

   11         IList<AuthorDto> GetAuthorsBy(string userId);

   12     }

 

In this example, I am using the Microsoft Enterprise Library Exception Policy class for logging exceptions.  This class is sealed with one static method class called HandleException.  Because of this, I cannot extract an interface, so for now I am going wrap this class in another concrete class that implements an interface that I can inject.

The interface looks like this:

    5     public interface ILogger

    6     {

    7         void LogException(Exception ex);

    8     }

 

The derived concrete class looks like this:

    6     public class MicrosoftLogger : ILogger

    7     {

    8         public void LogException(Exception ex)

    9         {

   10             ExceptionPolicy.HandleException(ex, "General");

   11         }

   12     }

 

Now I can mock my data provider and my logging class in my test, and I can also inject these classes into my web application later on.

So now the constructor of my service class looks like this:

   12     public class NewsItemService : INewsItemService

   13     {

   14         private readonly IDataProvider newsDataProvider;

   15         private readonly ILogger logger;

   16 

   17         public NewsItemService(IDataProvider newsDataProvider, ILogger logger)

   18         {

   19             this.newsDataProvider = newsDataProvider;

   20             this.logger = logger;

   21         }

 

So I mentioned that I am going mock the data provider class and to do this I am going use my mock tool of choice Rhino.Mocks.

   74         [TestMethod()]

   75         public void NewsItemService_get_latest_news_items_should_return_5_most_recent()

   76         {

   77             var mockRepository = new MockRepository();

   78             var dataProvider = mockRepository.StrictMock<IDataProvider>();

   79             var mockLogger = mockRepository.StrictMock<ILogger>();

   80 

   81             using (mockRepository.Record())

   82             {

   83                 Expect.Call(dataProvider.GetAllPublishedFrontPagePosts()).Return(PostRepository.GetNewsItems());

   84             }

   85 

   86             var numberOfItems = 5;

   87             var expected = 5;

   88             using (mockRepository.Playback())

   89             {

   90                 var target = new NewsItemService(dataProvider, mockLogger);

   91                 var items = target.GetLatestNewsItems(numberOfItems);

   92                 var currentDate = DateTime.MaxValue;

   93 

   94                 foreach (var item in items)

   95                 {

   96                     Assert.IsTrue(currentDate > item.DatePublished, currentDate.ToShortDateString() + " is not > " + item.DatePublished.ToShortDateString());

   97                     currentDate = item.DatePublished;

   98                 }

   99 

  100                 Assert.AreEqual(items.Count, expected, "Get the latest news does not eaqual 5");

  101             }

  102 

  103         }

 

In the test above instead of connecting to the database, I first tell Rhino Mocks to mock my data provider.  I pass it the IDataProvider interface and Rhino Mocks gives me back an instantiated data provider even though there is now derived concrete class involved.  I then do the same for the logging object.

In the Record section, I am telling Rhino Mocks that later on when I actually test my service object to expect it to make a call to the data provider class with a specific set of parameters (in this case I have no parameters) and when this occurs return my mocked result.  Once I have recorded all my expected calls that I want to mock, I can then proceed to the Playback method to test my service.

Inside the Playback I write my test as if I was actually connecting to a database and I assert that I got back 5 records sorted by the published date.

So I test and my test fails because I have not implemented the GetLatestNewsItems yet so let me do that.

   23         public List<NewsItemDto> GetLatestNewsItems(int numberOfItems)

   24         {

   25             try

   26             {

   27                 var items = newsDataProvider.GetAllPublishedFrontPagePosts();

   28                 return items != null ? items.OrderByDescending(i => i.DatePublished).Take(5).ToList() : null;

   29             }

   30             catch(Exception ex  )

   31             {

   32                 logger.LogException(ex);

   33                 throw;

   34             }

   35         }

 

Now I can test my object without being dependant on any concrete classes, and I can also inject my dependencies later on using Unity.

In my next post I will look into testing my controller class and some of the features MVC provides to do controller unit testing.

comments powered by Disqus