Beginning to Mock with Rhino Mocks and MbUnit - Part 2
page 2 of 5
by Ben Hall
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 36483/ 49

Mocking a Data Access Layer

One of the primary uses of mock objects is to replace dependent components while the system is being unit tested in order for the tests to execute in an isolated state without having to execute code on a different component. In this example we are going to be testing the business/service layer which makes calls to a database via a data layer. We are going to mock the database in order for our business layer tests to just test the business logic without calling into the database/data access layer. This has a number of benefits, for starters database access is slow and having slow running tests cause a loss in productivity and while the tests are running, your mind can drift and you lose focus on the task in hand. This problem can become important when practicing Test Driven Development (TDD) since if you are running your tests hundreds of times a day, any delay will have more impact on your time.  

If your tests are interacting with a database then they are more likely to be fragile tests and fail for reasons not relating to the code or requirements. For example, if your database server is down then all of your tests will fail. Also, if you only have a single database server then test runs could interact with each other causing irreproducible failures. One way to solve this would be to give everyone their own database server; however, this involves additional licensing costs and support.  Finally, if the database data has changed or is not what was expected by the tests, then the tests could fail as a result. 

If tests fail randomly, then it will take time to investigate the cause and could lead to a loss in confidence in the test suite which will harm the development of the project. Remember, our aim is to test a small single unit of code, not an entire section of the application. We should be testing the application end-to-end in integration and customer acceptance tests.  

Onto an example, in this simple application (the link to download the solution is at the top of the beginning of the article) we are required to pull a series of books out of the database which meet our requirements and then return them as a generic List<Book> collection. To start with, I am going to develop the business layer of the application and mock out the data access layer using Rhino mocks so there is no need to develop the database to start with and keep our business layer tests isolated and just test the actual business logic.

Our main aim when developing the system is to abstract away from the base implementation and to separate out business logic from the persistence framework, i.e. the database.

The first requirement we are going to implement is to return all the books in the database, as you can see in Listing 1.

Listing 1

[Test] 
public void GetListOfBooks() 
{ 
   Book expectedBook = new Book(1, "BeginningtoMock Part 2", 1.00); 
   tempMockDatabase.Add(expectedBook);  
   using (mocks.Record()) 
   { 
      Expect.Call(mockDataAccess.GetAllBooks()).Return(tempMockDatabase); 
   } 
   using (mocks.Playback()) 
   { 
      List<Book> bookList = bookService.GetAllBooks(); 
      Assert.AreEqual(1, bookList.Count); 
      Book actualBook = bookList[0]; 
      Assert.AreEqual(expectedBook, actualBook); 
   }
}

The code creates a book object (see Listing 3) and adds the object into our tempMockDatabase.  We then setup our mock object expectations, in this case when GetAllBooks() is called, we want it to return our tempMockDatabase object. In playback mode, we call GetAllBooks() on our BookService object, which handles all of our business information, and then simply assert that the count is equal to one and the book returned is what we expected.  

You may notice that some of the code is missing, for example creating the MockRespository and the MockDatabase. This is because I have refactored this into Setup and Teardown methods, which are then executed before and after each test. By doing this we are keeping our test code cleaner and easier to read.

Listing 2

private MockRepository mocks;
private List<Book> tempMockDatabase; 
private IBooksDataAccess mockDataAccess;
private BookService bookService;
[SetUp]
public void TestSetup()
{
   mocks = new MockRepository();
   mockDataAccess = mocks.CreateMock<BooksDataAccess>();
   tempMockDatabase = new List<Book>();
   bookService = new BookService(mockDataAccess);
}
 
[TearDown] 
public void TestTeardown()
{
   mocks = null;
   tempMockDatabase = null;
}

In the code we are creating a generic List<Book> collection which we use as our mock database, create a mock object for our IBookDataAccess, finally creating a BookService object giving our mock data access object as a parameter in the constructor.

Listing 3

public class Book
{
   public int ID;
   public string Title;
   public double Price;
   public Book(int id, string title, double price)
   {
      ID = id;
      Title = title;
      Price = price;
   }
}

Our book service code is then implemented.

Listing 4 

IBooksDataAccess db = nullpublic BookService(IBooksDataAccess dal) 
{ 
   db = dal; 
} 
public List<Book> GetAllBooks() 
{ 
   List<Book> localList = db.GetAllBooks(); 
   return localList; 
}

If we execute the test then it will pass successfully and our business layer successfully talks to our data access layer. One of the real nice things about mock objects is they come with debugger support. If you set a breakpoint on the localList variable in code Listing 4 then you can see that our list is populated with tempMockDatabase.

Our next requirement is to return all the books below a certain price. The test for this can be found below.

Listing 5

<a name=uq1x></a>[Test]
public void GetBooksBelow10()
{
   Book expectedBook = new Book(1, "BeginningtoMock Part 2", 1.00);
   tempMockDatabase.Add(expectedBook);
 
   using (mocks.Record())
   {
      Expect.Call(mockDataAccess.GetAllBooks()).Return(tempMockDatabase);
   }
 
   using (mocks.Playback())
   {
      List<Book> bookList = bookService.GetBooksWithPriceBelow(10);
      Assert.AreEqual(1, bookList.Count);
      Book actualBook = bookList[0];
      Assert.AreEqual(expectedBook, actualBook);
   }
}

Like before, we are inserting a book into our mock database and setup that GetAllBooks() should be called and it should return our database. In our test, we call GetBooksWithPriceBelow, and we expect that it should return 10.  In another test we can check to make sure it does not return books which are above the price given as a parameter.

Listing 6 

[Test]
public void GetBooksBelow10DoesntReturnAnyAbove()
{
   Book notExpected = new Book(2, "BeginningtoMock Part 2", 11.00);
   tempMockDatabase.Add(notExpected);
 
   using (mocks.Record())
   {
      Expect.Call(mockDataAccess.GetAllBooks()).Return(tempMockDatabase);
   }
 
   using (mocks.Playback())
   {
      List<Book> bookList = bookService.GetBooksWithPriceBelow(10);
      Assert.AreEqual(0, bookList.Count);
   }
}

This demonstrates one of the key advantages of using mock databases to replace our database during tests. Within our test we can easily setup the books we want our database to return and verify the actions based on the requirement being tested. Without using mocks, we would have to check in the database to make sure that it was configured correctly with our expected data. If it was not setup correctly, we would have to set it up and have to execute against the database and verify the response, finally cleaning up if any changes where made for the next test. This is a big overhead for the tests. We only want to verify that the business layer is working correctly and are not currently interested if the data layer works as we will have other tests for that. Having to setup and maintain a database is really unnecessary and costly. By using a mock database, our tests are more readable as all the information is within the same method, more predictable to test against and the tests are isolated and we can be sure that if any failures occur, they are due to the business logic and not something lower down.

I have written a test (Listing 8) which checks to make sure that when no argument is set on the constructor of the BookService class, that the db variable is populated with an object. However, in order to do this we either have to make the db variable accessible via a public interface or add a test hook - either of which I want to do. The solution is to add an assembly, InternalsVisibleTo attribute, as shown in listing 7, to our assembly under test and change the db variable type to internal. We can now access this variable from our external test assembly, but nowhere else.  I have spoken about this on my blog.

Listing 7

[assembly: InternalsVisibleTo("BeginningtoMock.Part2.Tests")]
...
internal IBooksDataAccess db = null;

We can then test the constructor.

Listing 8

[Test]
public void BookServiceDefaultCtorSetsUpDefaultDatabaseObject()
{
   BookService newBookService = new BookService();
   Assert.IsNotNull(newBookService.db);
}

One thing I have not mentioned yet is exceptions. Depending on how you handle exceptions will depend on your requirements. To illustrate how to use exceptions with Rhino Mocks for example, the data layer raises CannotConnectToDatabaseException and then the BookService should raise this up to the top level where we can handle it correctly.

The code in Listing 9 is the test to make sure that the book service handles the exception correctly and does not catch itself. Here we are using parts of the MbUnit framework in order to help the test. First, we are using an ExpectedExceptionAttribute to tell the test that if this exception is thrown the test should pass, otherwise fail.

When recording our mock implementation, instead of the call returning a mock database, we say it should throw the expectedException which we created.

Listing 9

[Test]
[ExpectedException(typeof(CannotConnectToDatabaseException))]
public void BookServiceHandlesDatabaseExceptionsCorrectly()
{
   CannotConnectToDatabaseException expectedException = 
      New CannotConnectToDatabaseException("Unable to connect to the database.");
   using (mocks.Record())
   {
      Expect.Call(mockDataAccess.GetAllBooks()).Throw(expectedException);
   }
   using (mocks.Playback())
   {
      bookService.GetAllBooks();
   }
}

By testing the exception handling, we are gaining a full picture of how the system will be implemented. By using mock implementations, we can simulate different situations which could be difficult when using a real database.

Now we have two methods on our business layer which are fully tested with 100% code coverage. Code Coverage is a metric used to determine how much of your test suite covers your code. If one of your tests executes the line of code, then it is included. If the line of code is not executed by the test suite then it is not included and this determines the percentage of code covered. 

NCoverExplorer is a free application that shows you the code which has not been executed.  Below is a screenshot of the application showing the results of our test run. As you can see, the BookService and Book classes have 100% code coverage, which is excellent as 80% is what we should at least be aiming for, however our BooksDatabase has 0% coverage. This means the application does not have any chance of working in production. Remember, mock objects are for test purposes only and only ever exist in the test suite, in our production application we use real implementations.

Figure 1

This is where we face a problem with mock objects. We have successfully mocked our data access layer within the business layer; however, we now need to test and implement our actual data layer. Everyone has their own way of doing this. On the outset, you have two choices. One would be to mock ADO.net, however, this is a difficult process with little gain, the other way would be to abstract away from the base implementations, such as SqlConnection, and mock those implementations. This leads to a number of mock objects in the system, however, it does allow us to have a level of interaction tested and verification that everything is talking to each other correctly. 

Another solution would be to use a real database. As we are testing the low data access layer, we could use a database. The reason using a real database is beneficial to the test suite is because we have refactored to a logical point in the system and if we mock the database we would need to rely on integration tests, which test the application end-to-end with real objects, to catch any errors. The database is part of the system and the layer, unlike the business process, so if a test fails in the data access layer, the problem is related to the data or accessing the data, and cannot be confused with business requirements.

As this series is about mocking and Rhino Mocks, I will abstract away from ADO.net and mock the implementations; however, you might find it more beneficial and easier to implement talking directly to the database.

In order to be able to implement the data access layer, I have split the implementation into two parts. One object, SqlDatabase, holds all the information relating to the database. This object implements the ISqlDatabase interface, as shown in Listing 10, which enables the implementation to be mocked.

Listing 10

public interface ISqlDatabase
{
   IDbConnection Connection { get; }
   string ConnectionString { get; }
   void CreateConnection();
   DataTable ExecuteReader(CommandType type, string sql);
   void SetConnectionString(string connString);
}

For this implementation, we have a property which holds a .Net database connection object and one which holds the connection string. While these properties are nothing special, we have an important method, ExecuteReader. For this method we pass in a command type and a sql string command and a DataTable is returned to the calling code. In our real implementation, this will connect to the database and execute the sql; however, we can mock out this code in other situations. The rest of the methods do not need to be mocked because they do not perform any action.

The reason I have split this information into a separate object is to make our objects less coupled. Our data access code can pass SQL statements to this object which can execute the commands against the server. This means we only have a single set of methods which control all access to the database, instead of it being mixed across the layer.  As our requirements grow, we can implement more methods on the SqlDatabase object.

While the other methods in the object are getters/setters, which can be tested in isolation, the ExecuteCommand can only be tested against a live server. While this does involve the method connecting to a database server, it is just a single method instead of our entire test suite. We can justify this as it means we can have our actual production code tested and we can be confident it works as expected. We can never have the same confidence with mocks.

Listing 11 demonstrates the test I wrote for the method. I am simply querying the sys.databases view in SQL Server in order to return some results which are populated into a datatable, which I then check to see if we have at least one row.

Listing 11

private string connectionString = 
   "Data Source=.;Initial Catalog=master;Integrated Security=True";
[Test]
public void ExecuteCommandAgainstServer()
{
   SqlDatabase sqlDb = new SqlDatabase(connectionString);
   sqlDb.CreateConnection();
 
   DataTable dataTable = sqlDb.ExecuteReader(CommandType.Text, 
      "use master; select * from sys.databases;");
 
   Assert.IsNotNull(dataTable);
 
   Assert.GreaterEqualThan(dataTable.Rows.Count, 1, "No records returned");
}

Our implementation code is shown below.

Listing 12

Public DataTable ExecuteReader(CommandType type, string sql)
{
   DataTable dt = new DataTable();
 
   try
   {
      IDbCommand cmd = Connection.CreateCommand();
      cmd.CommandType = type;
      cmd.CommandText = sql;
      Connection.Open();
 
      IDataReader dr = cmd.ExecuteReader();
      dt.Load(dr);
   }
 
   finally
   {
   if (Connection.State != ConnectionState.Closed)
      Connection.Close();
   }
 
   return dt;
}

With our database object complete, we can focus on the second half of the access layer, the BooksDataAccess object. This implements the IBooksDataAccess which we used when we were developing the business layer.

To start with, we need to implement the functionality to return all the books from the database.  Because we have already tested our implementation of ExecuteReader against a database, we can mock that method so that we are no longer dependent on the database and we can test against an in memory database (which in our case is a DataTable).  Listing 13 is our test to make sure this works correctly. 

We start by creating a mock object for ISqlDatabase; we then create a temporary database, shown in listing 14, which we use as our results when the ExecuteReader is called. This simulates the same effect of it going off to the database, returning the recordset, populating a datatable and returning it to the calling code. After we have setup our exceptions, we create a BooksDataAccess passing in the mockDataAccess object to the constructor. We then execute GetAllBooks() methods and verify that the number of books returned is one, we knew it should contain one because we created the fake datatable.

Listing 13

[Test]
public void GetAllBooksExecutesAndReturnsBooks()
{
   MockRepository mocks = new MockRepository();
 
   ISqlDatabase mockDatabase = mocks.CreateMock<ISqlDatabase>();
 
   DataTable dt = CreateDataTable();
 
   string sqlStatement = "SELECT * FROM Books";
 
   using(mocks.Record())
   {
      mockDatabase.CreateConnection();
 
      Expect.Call(mockDatabase.ExecuteReader(CommandType.Text, 
          sqlStatement)).Return(dt);
   }
 
   using (mocks.Playback())
   {
      BooksDataAccess da = new BooksDataAccess(mockDatabase);
 
      List<Book> books = da.GetAllBooks();
 
      Assert.IsNotNull(books, "Books response is null");
 
      Assert.AreEqual(1, books.Count);
   }
}

Listing 14

internal DataTable CreateDataTable()
{
   DataTable dt = new DataTable();
   dt.Columns.Add("id"typeof(int));
   dt.Columns.Add("Title"typeof(string));
   dt.Columns.Add("Price", typeof(double));
 
   DataRow dr = dt.NewRow();
   dr[0] = 1;
   dr[1] = "Test";
   dr[2] = 10.12;
   dt.Rows.Add(dr);
 
   return dt;
}

Again, looking at our actual implementation of this method, you can see we are calling the ExecuteReader method on the database interface and then we convert our DataTable into a list of books.

Listing 15

public List<Book> GetAllBooks()
{
   string sql = GetAllBooksSql();
   database.CreateConnection();
 
   DataTable dataTable = database.ExecuteReader(CommandType.Text, sql);
 
   return CreateBookListFromDataTable(dataTable);
}
 
 
public List<Book> CreateBookListFromDataTable(DataTable dataTable)
{
   List<Book> bookList = new List<Book>();
 
   foreach (DataRow r in dataTable.Rows)
   {
      Book b = new Book((int)r[0], (string) r[1], (double) r[2]);
      bookList.Add(b);
   }
 
   return bookList;
}
 
internal string GetAllBooksSql()
{
   return "SELECT * FROM Books";
}

That is our implementation of data access complete. We did not have to implement the method to return books by price as that uses the GetAllBooks method from our data layer. 

Our application business and data layers have 100% code coverage and only hits the database once in all of our tests. Not bad going!  Let us move on and have a look at mocking different parts of the system.


View Entire Article

User Comments

Title: saf   
Name: saf
Date: 2012-12-06 12:46:37 AM
Comment:
saf
Title: SE   
Name: Rakib
Date: 2010-05-12 6:29:21 AM
Comment:
Its a very good article
Title: CannotConnectToDatabaseException   
Name: Ben Hall
Date: 2008-04-03 10:12:17 AM
Comment:
CannotConnectToDatabaseException is a custom exception I created in my production code (what is being tested) because I couldn't create a SqlException to raise from that side :)
Title: Listing 5: Expect.Call GetAllBooks but call GetBooksWithPriceBelow   
Name: Bert Vanpeteghem
Date: 2008-01-15 5:13:50 AM
Comment:
A question on Listing 5:
In the record-section: there is Expect.Call(mockDataAccess.GetAllBooks())...
And in the playback-section:
bookService.GetBooksWithPriceBelow(10);

Shouldn't the call to GetBooksWithPriceBelow also be in the record?
Title: S/W Engineer   
Name: Bill Campbell
Date: 2007-10-15 12:52:47 PM
Comment:
Here's a link to the first one:

http://aspalliance.com/1400_beginning_to_mock_with_rhino_mocks_and_mbunit__part_1
Title: Beginning to Mock Part 2 Needs a Part 1 Link   
Name: Roger Jennings
Date: 2007-10-15 11:01:12 AM
Comment:
There's no obvious link to Part 1 in the introduction, which recommends it for readers unfamiliar with mock frameworks.

--rj

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-25 8:42:48 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search