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 = null;
public 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.