AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1456&pId=-1
Beginning to Mock with Rhino Mocks and MbUnit - Part 2
page
by Ben Hall
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 36550/ 48

Introduction

This is the second in a series of articles covering the Rhino Mocks mock object framework for the .NET Framework. The aim of this series is to provide you with an understanding of how mock objects are used, how they can improve the quality of your unit tests and how mock objects can make the difficult to test sections of your application simpler to test.  

In this second part, I cover when mock objects might be useful and explain how to implement the common uses of mock objects in your unit tests. I discuss how to mock your data layer, which involves accessing both a database and a web service, when testing your business layer and how to actually implement the data layer keeping it both flexible and mockable.

If you do not have experience with mock frameworks and have not read the first part, then I recommend you read that first before coming back to this article.

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.

Web Service

Web services have similar problems to databases, for example the service might be down or unavailable when the tests are executing causing them to fail.  If the web service is external and not under our control, we are then faced with a whole host of new problems. If an API key is required to access the service, for example Microsoft Live API, then you have the problem of managing the keys in the different environments or using a single key for everything. Along with this, there might be a limit on the number of times you can access the web service or you have to pay per request, not great if you are using this as part of your test suite. Finally, each request depends on the bandwidth and the speed of the external site, causing our tests to slow down our entire test suite execution.

Based on the previous requirements, we now are required to integrate an external payment service to handle the credit card processing for customer orders. While we are implementing this to go over a soap web service, the principals are the same for any external service.  In the solution, which you can download at the top, I have included a web service; it does not contain any logic, but it serves as a placeholder.

The first step is to add a reference to the web service using Visual Studio. This creates a local implementation which we can call to access the method on the web service. As this code is auto generated, we can trust that it works and we do not need to test it. However, we do need to test the logic for accessing the service.

To implement this system, I am going to use a proxy object which will implement an interface and call the real web service. We then have a processor object which calls the methods on the proxy. This means that when we are testing the system, we can mock the proxy so the real web service is never called.

The first test we need to write, as shown in Listing 16, is one to make sure that if a proxy object is called into the constructor, it sets the internal variable. By testing this we can be confident that even a proxy object can be called.

Listing 16

[Test]
public void ProxyCanBeSetAsAParameter()
{
   PaymentProcessingProxy proxy = new PaymentProcessingProxy();
 
   PaymentProcessor pp = new PaymentProcessor(proxy);
 
   Assert.IsNotNull(pp.wsProxy);
}

The implementation and interface look like Listing 17.

Listing 17

internal IPaymentProcessing wsProxy;
 
public PaymentProcessor(IPaymentProcessing proxy)
{
   wsProxy = proxy;
}
 
public interface IPaymentProcessing
{
   bool TakePayment(int paymentId, int customerId, double amount);
}

Now we can write a test to verify that PaymentProcessor executes the TakePayment method on the IPaymentProcessing object. If we have code which calls the web service, then it will also need to be tested using the PaymentProcessor and the mock version of IPaymentProcessing.

Listing 18 

[Test]
public void TakePaymentViaPaymentProcessorUsingMockService()
{
   MockRepository mocks = new MockRepository();
 
   IPaymentProcessing mockProxy = mocks.CreateMock<IPaymentProcessing>();
 
   using(mocks.Record())
   {
      Expect.Call(mockProxy.TakePayment(1, 1,
          10.0)).IgnoreArguments().Return(true);
   }
 
   using(mocks.Playback())
   {
      PaymentProcessor pp = new PaymentProcessor(mockProxy);
   bool result = pp.TakePayment(1, 1, 10.0);
   Assert.IsTrue(result);
   }
}

So that is great, we have a PaymentProcessor which executes TakePayment correctly on an IPaymentProcessing object without having to interact with an external dependencies.  Now we just need to implement the real PaymentProcessingProxy which can connect to the web service.

Listing 19 shows the test. As with the Database, we have refactored the implementation enough so we can mock the code within the other parts of the code, however, to test the actual production code we need to execute against the real implementation.

Listing 19

[Test]
public void PaymentProcessorProxyExecutesCorrectlyUsingRealService()
{
   PaymentProcessingProxy proxy = new PaymentProcessingProxy();
 
   bool result = proxy.TakePayment(1, 1, 10.0);
 
   Assert.IsTrue(result);
}

The first time I executed this test it failed with the following error:

"TestCase 'WebServiceTests.PaymentProcessorProxyExecutesCorrectlyUsingRealService'
failed: Unable to connect to the remote server
System.Net.WebException
Message: Unable to connect to the remote server"

This error occurred because the web service was not running. If we were connecting to this directly within our business code and not using mock objects, the test would fail without ever testing the business logic. By using mock objects, we only have a single failing test in which we can easily identify the reason for failing, especially if the other two tests are passing, as the only thing it does is execute the external service. 

The implementation is shown below.

Listing 20

public class PaymentProcessingProxy : IPaymentProcessing
{
   public bool TakePayment(int paymentId, int accountId, double amount)
   {
      PaymentService ps = new PaymentService.PaymentService();
 
      return ps.TakePaymentFromAccount(paymentId, accountId, amount);
   }
}

This completes our implementation of the web service. We have three tests, two checking the PaymentProcessor and one checking our real proxy implementation. Our system is also much more flexible as none of the system is dependent on the actual web service, instead it depends on the PaymentProcessor and an object which implements IPaymentProcessing, but this could be a database or completely different web service.

Downloads
Summary

We have discussed how to isolate your business layer tests by mocking out the data access layer so it is reliable and the only test what it is supposed to. We then discussed how to actually test the data access layer, mocking out the database and web service for most of the tests, but then how we actually have to hit the database and web service in order for complete code coverage.

You can download the full code solution using the link at the top which contains all the tests, implementations and web service.  If you have any questions or comments, please feel free to contact me.

 


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-19 5:33:08 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search