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.