How To Unit Test – Using SubSonic and Rhino Mocks

“All problems in computer science can be solved by another level of indirection” Butler Lampson (Or Abstraction as some people refer to it as). 

This is one of the most used quotes within computer science and when it comes to talking about mock objects I think it is particularly true. 

As discussed in Part 1 and Part 2 of my articles on Rhino Mocks, attempting to mock part of the .Net framework is not worth the effort and it is better if you create a wrapper around the framework implementation and then mock the wrapper (adding another layer of abstraction).

In this post, I will outline one possible approach to using mock objects and Rhino Mocks with SubSonic.  There are other approaches, mainly focusing around MVC, however for this approach I want to keep it focusing on using SubSonic as a DAL without discussing MVC.

Scenario

In this post, I’m going to implement a rss aggregator/roller.  It will check blog posts, save any new posts into a database and then the application will pull the blog posts out of the database. 

The flow of data will be this:

RSS > Post Manager > Database > Post Manager ||  Application.

For the moment, I’m not interested in the UI, I will be building the API which could easily be used by an application.  The reason why I picked a blog aggregator is because it provides a interesting TDD/Unit Testing challenge.  Not only are we dealing with databases, which as we found out is difficult, but we are also pulling data from a 3rd party source via the internet.

Implementation

Let’s start implementing the solution.  For a RSS Aggregator, the most important thing really is to take a RSS Feed and process the contents.  Because we are using mocks, we can mock out the external RSS feed and just use a known valid response, at runtime this will be replaced with code which makes a call to the web server.  The most important thing to remember is to mock at the external boundary as its at this point we lose control over the execution.

Important considerations for the implementation

Before we start, there are some important considerations about the implementation which we should think about before writing tests/code.  One of the main things to remember is to keep everything isolated and focus on the task in hand, we need to make sure our methods and tests aren’t going off and breaking their boundaries.  When we are testing the Post Manager for obtaining the RSS Feeds, we don’t want to be hitting the database to see if it saves correctly as well. Next, we want to be sure we are mocking the correct object in the system.  There is no point mocking the Rss object if all it does is hold data, we want to be mocking the object directly related to the OUT (Object Under Test) which has it’s own set of related tests to ensure it works correctly.  The objects we generally want to mock out are objects which interact with external dependences, in this case the RssFeed on the web and the database.

Finally, we want to make sure that our tests are running independently of each other and cleaning up after themselves if they make any permanent charges.

With that said, The first requirement for the system is be able to get the xml for a url.

[Test]
public void GetXmlFromURL()
{
    RssFeed rss = PostManager.GetRssFeed(http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml);
    Assert.AreEqual(TestHelper.url, rss.Url);
}

The test simply says we want to access a static method on the PostManager called GetRssFeed and by giving it a url, we can return an RssFeed object. For my RSS objects, I will be using RSS.Net to save me implementing and testing the feed parsing (why build it when its already done).  I actually found this framework very easy to use and completely open source so you can modify it as you see fit (I did to make RssFeed.Url not read-only).  To implement the method on the PostManager, we call a method on the Rss.Net object.

public Rss.RssFeed GetRssFeed(string url)
{
    return RssFeed.Read(url);
}

Now we have a working implementation, it would be nice if we could mock obtaing the RSS instead of having to hit the rss feed every time we wanted to test the method.

[Test]
public void MockGettingRssFeed()
{
    //This is what we want our mock object to return to the calling code.
    RssFeed rss = TestHelper.CreateMockRSS(TestHelper.url);
    MockRepository mocks = new MockRepository();
    IFeedGetter feedGetter = mocks.CreateMock();

    using (mocks.Record()) //Setting up what we expect the mock object to do.
    {
        Expect.Call(feedGetter.GetFeed(TestHelper.url)).Return(rss);
    }

    using (mocks.Playback()) //Performing the test
    {
        PostManager.FeedGetter = feedGetter;
        RssFeed actualRss = PostManager.GetRssFeed(TestHelper.url);

        Assert.GreaterThan(rss.Channels.Count, 0);
        Assert.AreEqual(TestHelper.url, rss.Channels[0].Link.AbsoluteUri);
    }
}

In order for obtaining the RSS feed to be mocked out, I moved the GetFeed method into a IFeedGetter interface (implemented as FeedManager) and added a FeedGetter property to the PostManager object.  This allows me to use the default object (FeedManager) in the real system without changing the code, however during unit testing (or in the future) I can replace the default implementation with my own – in this case a mock IFeedGetter.

private static IFeedGetter feedGetter = new FeedManager();
public static IFeedGetter FeedGetter
{
    get { return feedGetter; }
    set { feedGetter = value; }
}

In order to keep the test code cleaner, I extracted the creation of the mock RssFeed object into a TestHelper method, this can then be called from multiple locations within my test code without having to manually create an object each time.  There is also a unit test associated with this method to make sure it works correctly.

Our implementation of FeedGetter and PostManager is complete.  Using Rss.Net allowed us to cut the implementation time.  Moving on, we need to be able to save this RssFeed object into the database. 

If you are not aware, an RSS is made up of general information about the feed (RssChannel) and then a number of items (RssItem).  To save the feed, the first thing we need to do is save the RssChannel object.

[Test]
public void InsertFeedDetailsIntoDatabase()
{
    using (mocks.Record())
    {
        Expect.Call(database.InsertFeedDetails(rssFeed.Channels[0])).Return(1);
    }

    using (mocks.Playback())
    {
        int recordID = DatabaseManager.InsertFeedDetails(rssFeed.Channels[0]);

        Assert.GreaterThan(recordID, 0);
    }
}

This method simple says that a method on the database object should accept an RssChannel object and return a 1 if its inserted correctly.  We them access this method via a DatabaseManager object.  For the test to be shorter, I moved a lot of the code into the [Setup] for the test fixture.

RssFeed rssFeed;
MockRepository mocks;
IDataStore database;

[SetUp]
public void TestSetup()
{
    rssFeed = TestHelper.CreateMockRSS(TestHelper.url);
    mocks = new MockRepository();
    database = mocks.CreateMock();
    DatabaseManager.DataStore = database;
}

Notice I’m setting the DataStore of the DatabaseManager to the IDataStore mock object via a property. One thing which I want to test for is to make sure that if the property hasn’t been setup correctly, then the correct exception is thrown.  This just saves any nasty surprises later.

[Test]
[ExpectedArgumentNullException]
public void ThrowExceptionOnSaveFeedIfNoDataSource()
{
    DatabaseManager.DataStore = null;
    DatabaseManager.SaveFeed(rssFeed);
}

Moving on, the next item to implement is saving an actual RssItem.   The test just makes sure that the InsertFeedItem is called twice, once for each RssItem in the collection.

[Test]
public void InsertFeedItemsIntoDatabase()
{
    int channelId = 1;
    using (mocks.Record())
    {
        Expect.Call(database.InsertFeedItem(channelId, rssFeed.Channels[0].Items[0])).Return(1);
        Expect.Call(database.InsertFeedItem(channelId, rssFeed.Channels[0].Items[1])).Return(2);
    }

    using (mocks.Playback())
    {
        bool result = DatabaseManager.InsertFeedItems(channelId, rssFeed.Channels[0].Items);
        Assert.IsTrue(result);
    }
}

Now we have both parts to save the feed in place, we can write another method which tests we can do both.

[Test]
public void InsertFeedIntoDatabase()
{
    int channelID = 1;

    using (mocks.Record())
    {
        Expect.Call(database.InsertFeedDetails(rssFeed.Channels[0])).Return(channelID);
        Expect.Call(database.InsertFeedItem(channelID, rssFeed.Channels[0].Items[0])).Return(1);
        Expect.Call(database.InsertFeedItem(channelID, rssFeed.Channels[0].Items[1])).Return(2);
    }

    using (mocks.Playback())
    {
        bool result = DatabaseManager.SaveFeed(rssFeed);
        Assert.IsTrue(result);
    }
}

Now our DatabaseManager is setup correctly with the methods we want to expose to the public which meet the requirements, we can move onto implementing our IDataSource which will actually store the information.  For this, we are using SubSonic to save all of our Rss information into the database.

The first problem we face is converting our Rss.Net objects into the ActiveRecord SubSonic objects for the database, a method is required to covert between the two objects. Inside of SubSonicTests.cs, we have a test to make sure this is done correctly on our dataSource object.

[Test]
public void ConvertFromRssChannelToSubSonicRssChannel()
{
    RssChannel rssChannel = rss.Channels[0];
    //Returns a SubSonic object.
    MockSubSonic.DAL.RssChannel subSonicChannelObject = dataSource.ConvertRssChannelObject(rssChannel);

    Assert.AreEqual(rssChannel.Title, subSonicChannelObject.Title);
    Assert.AreEqual(TestHelper.url, rssChannel.Link.AbsoluteUri);
    Assert.AreEqual(rssChannel.Link.AbsoluteUri, subSonicChannelObject.Uri);
    Assert.AreEqual(rssChannel.Description, subSonicChannelObject.Description);
    Assert.AreEqual(rssChannel.PubDate, subSonicChannelObject.PubDate);
    Assert.IsTrue(subSonicChannelObject.IsNew); //it shouldn’t save it.
}

We can then implement our solution to save the details into the database, first the details:

[Test]
public void InsertRssChannelIntoDatabase()
{
    int channelId = dataSource.InsertFeedDetails(rss.Channels[0]);
    Assert.AreEqual(channelId, 1);
}

Then the RssItems.

[Test]
public void InsertRssItemIntoDatabase()
{
    int channelId = dataSource.InsertFeedDetails(rss.Channels[0]);
    Assert.GreaterEqualThan(channelId, 0);

    int itemId = dataSource.InsertFeedItem(channelId, rss.Channels[0].Items[0]);
    Assert.AreEqual(itemId, 1);
}

We are not using mock objects at this point because we have refactored to the lowest reasonable point.  Any more and it gets too confusing and becomes pointless. We have cut a lot of the database access out by making it mockable in the first place, having these methods hit the database isn’t going to be a great problem.  So we don’t get any problems in the future, we need to make sure we clean up after ourselves.

[SetUp]
public void TestSetup()
{
    rss = TestHelper.CreateMockRSS(TestHelper.url);
    dataSource = new SubSonicDataStore();
    ExecuteSql(“TRUNCATE TABLE RssChannel”);
    ExecuteSql(“TRUNCATE TABLE RssItem”);
}

[TearDown]
public void TestTearDown()
{
    rss = null;
    dataSource = null;
    ExecuteSql(“TRUNCATE TABLE RssChannel”);
    ExecuteSql(“TRUNCATE TABLE RssItem”);
}

This is the bases for the rest of the implementation of the database.  We use SubSonic within the DataSource without any tricks or mock objects to ensure its working correctly, however in any later which isn’t interacting directly with the database, we are using mock objects.  The other part of the system which I will cover is getting information from the database.

The test below is to ensure that our DataManager object can return RssItems for a Channel based on the ChannelID.

[Test]
public void GetFeedItemsForChannelFromDatabase()
{
    int channelId = 1;
    using (mocks.Record())
    {
        Expect.Call(database.GetRssItemsForChannel(channelId)).Return(rssFeed.Channels[0].Items);
    }

    using (mocks.Playback())
    {
        RssItemCollection rssFeeds = DatabaseManager.GetRssFeedsForChannel(channelId);
        Assert.IsNotNull(rssFeeds);
        Assert.AreEqual(2, rssFeeds.Count);
    }
}

The SubSonic test for this requirement is below.  To start with we are inserting the channel and two feeds from our mock object into the database so we can actually return something.  We then obtain the collection from our database and to ensure all the feeds we got are correct we are verifying them within a foreach loop against our known TestHelper Rss Mock object which we inserted.

[Test]
public void GetRssChannelFeeds()
{
    int channelId = dataSource.InsertFeedDetails(rss.Channels[0]);
    dataSource.InsertFeedItem(channelId, rss.Channels[0].Items[0]);
    dataSource.InsertFeedItem(channelId, rss.Channels[0].Items[1]);

    RssItemCollection rssColl = dataSource.GetRssItemsForChannel(channelId);
    Assert.AreEqual(2, rssColl.Count);

    int i = 0;
    foreach (RssItem item in rssColl)
    {
        Assert.AreEqual(rss.Channels[0].Items[i].Title, item.Title);
        Assert.AreEqual(rss.Channels[0].Items[i].PubDate.ToString(), item.PubDate.ToString());
        Assert.AreEqual(rss.Channels[0].Items[i].Link.AbsoluteUri, item.Link.AbsoluteUri);
        Assert.AreEqual(rss.Channels[0].Items[i].Description, item.Description);
        Assert.AreEqual(rss.Channels[0].Items[i].Author, item.Author);
        i++;
    }
}

The advantage to inserting all the information at the start of the test is that we know what is in the database and what to expect when its returned.  If we relied on the contents of the database, then other tests could change the values or someone could come along and clean out the database – our tests would then fail as what we expected wasn’t what was actually their. This makes debugging the failure even more difficult as we have to investigate why it failed as it might not be because of our implementation but conflicting test runs.

If your interested in how the rest of the system is implemented, please download the project solution at the bottom of the post.

Passing Tests

After we have implemented our API, we have a number of tests which should all pass green.

MockSubSonicPassing

MockSubSonicCoverage

The reason we haven’t got 100% code coverage is because one or two lines of error handling I inserted which wasn’t tested against (for example, if a user couldn’t be found).  In a real system, we would need to test against these situations.  I have exclude the code generated by SubSonic and the RSS.Net project from the report, as that’s not directly related to the system and doesn’t provide a good indication of our unit test coverage.

To actually make sure it all works correctly, I created a simple Console Application which calls all the methods.

static void Main(string[] args)
{
    Console.WriteLine(“Welcome UserA to MockSubSonic”);
    User u = new User(“UserA”, “PassA”, “[email protected]”);
    DatabaseManager.SaveUser(u);

    Console.WriteLine(“Saving Feed”);
    DatabaseManager.SaveFeed(RssFeed.Read(“
http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml”));

    Console.WriteLine(“Subscribing”);
    bool result = DatabaseManager.SubscribeUserToFeed(u.Name, “
http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml”);

    if(result)
        Console.WriteLine(“You successfully subscribed to
http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml”);
    else
        Console.WriteLine(“You failed to subscribed to
http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml. Sorry”);

    RssFeedCollection feeds = DatabaseManager.GetUsersRssFeedCollection(u.Name);
    Console.WriteLine(“You are now subscribed to: ” + feeds.Count);
    Console.WriteLine(“First one is : ” + feeds[0].Channels[0]);

    Console.ReadLine();
}

image

Another way of doing this would have been a more Integrated style test using MbUnit.  It’s the kind of the same as the tests we have wrote before, however it touches more/all parts of the system to verify it is working correctly together.

Summary

I hope you have found this post useful and interesting if you are looking at SubSonic and mock objects.  The important thing to remember when mocking SubSonic is to wrap the SubSonic object inside your own access layer (in this case SubSonicDataStore : IDataStore).

Feedback/comments welcome!

Resources:

Code – Unit Tests, API, Application, SQL zip

SubSonic

Rhino Mocks

MbUnit

Rss.Net

Technorati Tags: , , , ,

3 thoughts on “How To Unit Test – Using SubSonic and Rhino Mocks”

  1. Hi Ben,

    In SubSonicTests.cs at SaveFeedWithInvalidDate() this test fails at line 212:

    Assert.AreEqual(“01/01/1970 00:00:00”, subSonicChannelObject.PubDate.ToString()); on my hungarian Windows XP because of date format difference.

    I changed this line to code below, and it works:
    Assert.AreEqual(“01/01/1970 00:00:00”, subSonicChannelObject.PubDate.ToString(CultureInfo.GetCultureInfo(“en-GB”)));

    Regards,
    S.

  2. Hey Ben,

    I’ve also been working on trying to do unit testing with Rhino and SubSonic. I have an adapter library that allows me to use the SubSonic framework through interfaces, which makes it pretty simple to do unit testing. Details and source are posted on my blog here.

Leave a Reply to Sandor Korozsi Cancel reply

Your email address will not be published. Required fields are marked *