NUnit, like all the other XUnit family of testing frameworks, all provide roughly the same functionality. They allow you to write test code in the same language as your production code, and they provide a collection of test runners (drivers) to automate the process of running tests and reporting results.
The basic approach to using NUnit is:
NUnit provides two test runners (drivers): A GUI application called nunit-gui.exe and a console-based application called nunit-console.exe. The GUI application is better for verifying your own code in your development environment while you work on it. The console version is more useful for automated testing environments on your build and integration servers.
NUnit is all about running test cases. Recall that a test case is really a combination of specific inputs and expected results. In other words, you must specify the inputs and you must specify the expected output for these inputs. If after running the test, the actual output does not match the expected output, then the test has succeeded and you have discovered an error. If on the other hand, after running the test, the actual output did match the expected output, then the test has failed and you have not discovered any error (the use of succeeded and failed corresponds to the fact that testing is all about finding errors - you succeed when you find errors!)
The above approach of specifying inputs and expected output, and then noting whether the actual output matches the expected output, corresponds in NUnit to assertions. NUnit provides a static object called Assert that provides a series of static methods all of which evaluate to a boolean condition. For example, Assert.IsTrue is used and interpreted as follows:
Assert.IsTrue( obTriangle.IsEquilateral(100,100,100) );
Here you are testing a function called IsEquilateral and supplying three inputs all have the same value. The expected output is true because you are using Assert.IsTrue. When NUnit runs this test, it will invoke IsEquilateral, and if the function returns true (actual output) then the actual output matches the expected output (Assert.IsTrue implies an expected output of true).
The basic approach is to write a public testing class for each unit you wish to test. The 'unit' may be anything from a simple class to a system component containing tens of classes (i.e., a data access layer). Given that the class is used to test a unit, it serves to 1) create a subset of tests that can be run as a group, and 2) to provide a level of granularity for controlling the lifecycle and context of a test.Public methods within this class are test methods and typically you would have a test method for each test case. The general layout is shown below:
/* Notes
1. Keep all test code in separate projects but within the
same solution. For example, for project
Triangle, there is another project called TriangleTest
2. All test projects must references NUNIT.FRAMEWORK.DLL
*/
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace TriangleTest
{
/* A test class is a collection of
logically grouped tests, and as such must be
annotated with the [TestFixture] attribute. The test class
must be public and has
a default constructor. It is important to note that the
default constructor should
not have any side-effects as NUnit may construct the class
multiple times in when
running tests */
[TestFixture]
public class TriangleCheckerTest
{
private User obUser = null;
/* Each test
method must be able to exist and run on its own. Tests should not
be affected by side effects or state
set by other tests, not should they cause
side effects to occur. In other
words, tests should live in a stateless
environment. This ensures that we are
only testing one functional unit at a time
and that we do not propagate error
conditions between tests.
All this means that whenever a test
starts, it should start with a clean initial
state, and when the test finishes, it
should clean up after itself. [Setup] method
runs before each [Test] method, and [TearDown]
method runs after each [Test] method.
[Setup] usually contains construction
or initialization code, whereas [TearDown]
contains
destruction/de-initialization code */
[SetUp]
public void Initialize()
{
/* Create an object (each [Test] method will get a newly constructed object),
or initialize
a test database to a known state (each [Test] method will get a
properly
initialized database) */
obUser = new
User();
}
[TearDown]
public void DeInitialize()
{
/* Dispose of the object or clean up the database, etc. */
obUser.Dispose()
}
/* [TestFixtureSetup]
and [TestFixtureTearDown] are similar in concept to [Setup]
and [TearDown] except that they apply
at the test class level rather than at the
test method level. This means that [TestFixtureSetup]
and [TestFixtureTearDown]
should be used to initializer/deinitialise
what ever is required before running
any test in the test class. Examples
include creating/deleting a database schema,
or reading configuration file
entries, etc. (Basically anything that needs to be
done only once before testing as a
whole can begin) */
[TestFixtureSetup]
public void InitTesT()
{
// Create data base schema for testing
}
[TestFixtureTearDown]
public void DeInit()
{
// Delete the testing schema from the database
}
/* A test class
is a collection of tests. Each test is actually a test method
which must be annotated with [Test],
returns void, and takes no parameters
Semantically, a [Test] method should
really correspond to a test case.
If all assertions within a [Test]
method pass, the test passes. If any assertion
fails, the test fails. Also all
unhanded exceptions are also reported as test
failures */
[Test]
public void IsUserRegistered()
{
/* Specify input and expected output, and then run test to see if actual
output
matches the expected output. */
int nID =
123;
Assert.AreEqual(obUser.GetUserName(nID), "Yazan"); // Input is nID, expected
output is "Yazan"
/* If an assertion fails, the method does not return and
an error is reported
by NUnit. If
a test contains many assertions, any that follow the one that
failed will
not be executed. For this reason, it is best to have one assertion
per test */
Assert.IsTrue(
... ); // Should not have this assert
}
/* Recall that
all unhanded exceptions are also reported as test failures. However,
it may be that an exception is
actually an expected results. The [ExpectedException]
attribute allows you to treat the
exception as an expected results */
[Test]
[ExpectedException(typeof(InvalidUserOperation))]
public void UpdatedUser()
{
Assert.IsTrue(obUser.UpdateDatabase());
}
/* Test cases
that are not yet fully implemented can be ignored by NUnit drivers if
they the test methods are annotated
with [Ignore] */
[Test]
[Ignore("Not completed yet")]
public void DeleteUser()
{
// To be implemented
}
/* [Explicit]
causes a test to be ignore unless explicitly selected for running */
[Test]
[Explicit]
public void InsertUser()
{
// ...
}
/* Tests can
also be categorized into groups for each of testing. The GUI version of NUnit
has a separate tab for 'Categories'
that provides a visual indication of which groups are
selected */
[Test]
[Category("No database access")]
public void SomeTest()
{
// ...
}
}
}
The following shows how to test a simple program TriangleChecker which takes side lengths and determines if the sides correspond to an equilateral (all sides equal), isosceles (two sides are equal), or scalene (no sides equal) triangle. TriangleChecker implementation has errors and the unit tests should identify these errors:
// The implementation
logic is incorrect and unit tests should identify these errors
public class TraingleChecker
{
// Examines if all sides are equal
public bool IsEquilateral(int a, int b, int c)
{
if ((a == b) && (b == c))
return true;
else
return false;
}
// Examines if two sides are equal
public bool IsIsosceles(int a, int b, int c)
{
if ((a == b) || (a == c) || (b == c))
return true;
else
return false;
}
// Examines if no sides are equal
public bool IsScalene(int a, int b, int c)
{
if ((a != b) && (b != c))
return true;
else
return false;
}
}
[TestFixture]
public class TriangleCheckerTest
{
private Triangle.TraingleChecker m_obTriangleChecker = null;
[SetUp]
public void Initialize()
{
m_obTriangleChecker = new
TraingleChecker();
}
[TearDown]
public void DeInitialize()
{
m_obTriangleChecker = null;
}
[Test]
[Category("Equialteral - Valid")]
public void ValidEquilaterals()
{
// Define input
conditions
int a = 10;
int b = 10;
int c = 10;
// Execute a
test case with inputs a, b, and c with an expected output of
// true (Assert.IsTrue means that we
are expecting the output to be true)
Assert.IsTrue(m_obTriangleChecker.IsEquilateral(a, b, c), "Test message");
}
[Test]
[Category("Equialteral - Valid")]
public void ValidEquilateralUpperBoundary()
{
// Setup inputs
int a = 255;
int b = 255;
int c = 255;
// Execute a
test. The expected result should be true as this is a valid triangle
Assert.IsTrue(m_obTriangleChecker.IsEquilateral(a, b, c));
}
[Test]
[Category("Equialteral - Valid")]
public void ValidEquilateralLowertBoundary()
{
// Setup inputs
int a = 1;
int b = 1;
int c = 1;
// Execute a
test. The expected result should be true as this is a valid triangle
Assert.IsTrue(m_obTriangleChecker.IsEquilateral(a, b, c));
}
[Test]
[Category("Equialteral - InValid")]
public void InValidEquilateralsLowerBoundary()
{
// Define input
conditions
int a = 0;
int b = 0;
int c = 0;
// Execute a
test. The expected result should be false as this is an invalid triangle
Assert.IsFalse(m_obTriangleChecker.IsEquilateral(a, b, c));
}
[Test]
[Category("Equialteral - InValid")]
[ExpectedException(typeof(Exception))]
public void InValidEquilateralsNegativeBoundary()
{
// Define input
conditions
int a = -1;
int b = -1;
int c = -1;
// We attempt
to examine if negative values for all three sides would pass an an
// equilateral triangle. It should
generate an exception as there is no such
// triangle, hence the [ExcpectedException]
attribute. The way .NET handles exceptions
// means that control should
immediately pass out of the IsEquilateral method to the
// calling method, in this case the
NUnit test method, which in turh passes out to the
// NUnit test runner. NUnit examines
the exception to see if it is of the expected
// type and if so passes the test.
Otherwise the test fails and the exception is reported.
// Since control passed to the NUnit
runner, the Assert.Fail line should never be called.
// If Assert.Fail does get called,
then IsEquilateral did not generate an exception and
// Assert.Fail will cause the test to
fail.
m_obTriangleChecker.IsEquilateral(a, b, c);
Assert.Fail( "Tested for negative
values but no exception was thrown" );
}
}
Consider the example of a business logic library that uses a data access component to communicate with a database server as shown below:
Although the binding between the business logic library and the data access component is interface-based, the business logic library still needs to be able to access properties and methods on the data access component during unit testing, and that's where the trouble begins: the data access component needs a connection to the database server to work. This means that you need to configure the data access component to open a connection to the right database and you almost certainly need to set up a test instance of the database (you never want to test on a production database!) With SQL Server 2005, setting up a test database is quite manageable, but what if the database server was mainframe-based?
Configuring the DAL is not the only problem. If the DAL is part of the development project, it may not even exist yet, and even if it does, is most likely will be under development with a number of known/unknown bugs. In this case, you risk a possible unit test failure due to bugs in the DAL even if the code under test has no defects.
For unit testing with mock objects I use NUnit and NMock, which can be obtained from www.nunit.org and www.nmock.org, respectively. The download for NUnit installs NUnit as a stand-alone application with GUI-based and console-based drivers. The download for NMock however, consists of an assembly nmock.dll that can be references from testing projects directly.
It is worth noting that NMock uses reflection to create mock implementations of interfaces at run time. To use NMock it is therefore necessary to code against interfaces instead of implementations. As this is already a well-regarded best practice, NMock can help enforce this important design technique.
The following figure shown how NUnit and NMock interact:
A common approach to unit testing with mock objects is:
Create instances of mock objects.
Set state in mock objects.
Set expectations in mock objects.
Invoke domain code with mock objects as parameters.
Verify consistency in mock objects.
This approach the test makes clear what the domain code is expecting from its environment.
class Basket denotes a basket in an e-commerce application. Note the IOC pattern used within this class:
public interface IShoppingDataAccess
{
string GetProductName(int
productID);
int
GetUnitPrice(int productID);
BasketItem[] LoadBasketItems(Guid basketID);
void
SaveBasketItems(Guid basketID, BasketItem[] basketItems);
}
public class Basket
{
private ArrayList basketItems_;
private Guid basketID_;
private IShoppingDataAccess dataAccess_;
public Basket()
{
string typeName = ConfigurationSettings.AppSettings["IShoppingDataAccessType"];
Type t = Type.GetType(typeName);
IShoppingDataAccess dataAccess = (IShoppingDataAccess)Activator.CreateInstance(t);
Initialize(dataAccess);
}
public Basket(IShoppingDataAccess dataAccess)
{
Initialize(dataAccess);
}
public void AddItem(BasketItem item)
{
this.basketItems_.Add(item);
}
public void Save()
{
this.dataAccess_.SaveBasketItems(this.basketID_,
(BasketItem[])this.basketItems_.ToArray(typeof(BasketItem)));
}
public decimal CalculateSubTotal()
{
decimal subTotal = 0;
foreach(BasketItem item in this.basketItems_)
{
subTotal += item.GetPrice();
}
return subTotal;
}
private void Initialize(IShoppingDataAccess dataAccess)
{
this.dataAccess_ = dataAccess;
this.basketItems_ = new ArrayList();
this.basketID_ = Guid.NewGuid();
}
}
class BasketItem denotes a single item within the basket:
public class BasketItem
{
private decimal unitPrice_;
private int productID_;
private int quantity_;
private IShoppingDataAccess dataAccess_;
private string productName_;
public BasketItem(int productID, int quantity)
{
string typeName =ConfigurationSettings.AppSettings["IShoppingDataAccessType"];
Type t = Type.GetType(typeName);
IShoppingDataAccess dataAccess =
(IShoppingDataAccess)Activator.CreateInstance(t);
Initialize(productID, quantity, dataAccess);
}
public BasketItem(int productID,
int quantity,
IShoppingDataAccess dataAccess)
{
Initialize(productID, quantity, dataAccess);
}
public decimal UnitPrice
{
get{return this.unitPrice_;}
}
public int ProductID
{
get{return this.productID_;}
set
{
this.productID_ = value;
this.unitPrice_ =
this.dataAccess_.GetUnitPrice(this.productID_);
this.productName_ =
this.dataAccess_.GetProductName(this.productID_);
}
}
public int Quantity
{
get{return this.quantity_;}
set{this.quantity_ = value;}
}
public string ProductName
{
get{return this.productName_;}
}
public decimal GetPrice()
{
return this.unitPrice_ * this.quantity_;
}
private void Initialize(int productID,
int quantity,
IShoppingDataAccess dataAccess)
{
this.dataAccess_ = dataAccess;
this.ProductID = productID;
this.Quantity = quantity;
}
}
We start by looking at a very simple unit test which demonstrates how to create a mock instance of the IShoppingDataAccess interface:
// Create a DynamicMock
object. This object does not implement the supplied type (IShoppingDataAccess),
// but is an object which contains information (via reflection) about the
methods of IShoppingDataAccess
// and can emit an implementation of that interface
DynamicMock mock = new DynamicMock(typeof(IShoppingDataAccess));
// DynamicMock objects use reflection to emit an
implementation of the desired type and expose it through
// the MockInstance property. Here the dynamically created implementation of
IShoppingDataAccess interface
// is passed to the basket constructor as per the
Inversion of Control
design pattern
Basket b = new Basket((IShoppingDataAccess)mock.MockInstance);
// b.Save internally calls SaveBasketItems method on its
internal IShoppingDataAccess member (a mock).
// As nothing was specified in the setup call, the DynamicMock object emits an
empty implementation
// of SaveBasketItems.
// Note: Because SaveBasketItems returns void, we are OK with this default
behaviour, otherwise, we
// would have to setup the mock object to so as to know what value to return
when SaveBasketItems
// is called
b.Save();
The default behaviour of a mock instance for methods with return values is to return null for every method call that returns a reference type, and to return the default value from every method that returns a value type. This is adequate for void methods but is useless for methods with return values. If you examine the BasketItem constructor you will note that the ProductID property is being set which results in two calls to the IShoppingDataAccess interface. Because both calls return a value, a NullReferenceException would have been thrown by the GetUnitPrice method.
When dealing with methods that return values it becomes necessary to instruct the mock object what to return as shown below:
// Create a mock object
DynamicMock mock = new DynamicMock( typeof(IShoppingDataAccess) );
// Setup results. The following configures GetUnitPrice so
that it returns 100 every time it is called.
// GetProductName is also configured to return "Effective C#" every time it is
called.
// Note: The first two parameters to SetupResult supply the method name and the
return value. The
// third parameter is an array of type that is used to identify which method
should be called. This
// is requires because a method may have many overloads, and the array of types
is required in order
// to infer the correct method via its unique signature (a method's signature
consists of its name,
// and the type and kind of each parameter)
mock.SetupResult( "GetUnitPrice", 100, typeof(int) );
mock.SetupResult( "GetProductName", "Effective C#", typeof(int) );
// Invoke the method under test
BasketItem item = new BaksetItem( 1, 2, (IShoppingDataAccess)mock.MockInstance));
...
The above calls to SetupResult ensures that any call to a configured method will always return a specified value, no matter what parameter was used in the method call. For example, if you try to create two basket items, you would get two BasketItem objects with the same unit price and product name (because BasketItem constructor calls the ProductID property which calls GetUnitPrice and GetProductName, which are configured to return fixed values.)
NMock solves this problem via the ExpectAndReturn method. This method is similar to the SetupResult method but instead supplies a value rather than a type for the method in question. For example:
// Create a mock object
DynamicMock mock = new DynamicMock( typeof(IShoppingDataAccess) );
// Setup results and expectations
mock.ExpectAndReturn( "GetUnitPrice", 100, 1
);
mock.ExepctAndReturn( "GetProductName", "Effective C#",
1 );
mock.ExpectAndReturn( "GetUnitPrice", 200, 10
);
mock.ExepctAndReturn( "GetProductName", "Effective C++",
10 );
// Create two different basket items
BasketItem item1 = new BaksetItem( 1, 2, (IShoppingDataAccess)mock.MockInstance));
BasketItem item2 = new BaksetItem( 10, 2, (IShoppingDataAccess)mock.MockInstance));
// ...
Note that NMock is expecting only two calls to GetUnitPrice and two calls to GetProductName in the order specified. If any of these methods is called with a parameter value other than what is supplied, or if any of these methods is called in a different order, a VerifyException will be fired. Any extra calls would also result in an exception. In other words, you can use ExpectAndReturn to validate that a method was called the expected number of times, with the expected parameters, and in the expected order. With this approach the unit test not only verifies that the unit returns the correct values, but that it communicates any external dependencies as designed. Therefore, letting the mock object participate in the test this way is a good idea.
Consider the first example which is shown below:
// Create a DynamicMock
object
DynamicMock mock = new DynamicMock(typeof(IShoppingDataAccess));
// The dynamically created implementation of
IShoppingDataAccess interface is passed to the basket
// constructor as per the
Inversion of Control
design pattern
Basket b = new Basket((IShoppingDataAccess)mock.MockInstance);
// b.Save internally calls SaveBasketItems method on its
internal IShoppingDataAccess member (a mock).
// As nothing was specified in the setup call, the DynamicMock object emits an
empty implementation
// of SaveBasketItems.
// Note: Because SaveBasketItems returns void, we are OK with this default
behaviour, otherwise, we
// would have to setup the mock object to so as to know what value to return
when SaveBasketItems
// is called
b.Save();
In the code above no expectation was set for SaveBasketItem which is called internally by the Save method. You should change this behaviour by setting the Strict property to true. This causes any call that does not have an expectation to throw a VerifyException which will in turn fail the test.
However, when you attempt to set an expectation for SaveBasketItem, you will note that the first parameter is a GUID, which is the ID of the basket. On examination of the Basket class, you will note that the basket ID is not accessible externally. Even though this is a design error (should provide a property to provide access to the ID), this design error is used to illustrate the usage of constraints. Constraints solve the following problem: How can you define an expectation if you do not know the value of a parameter in advance? NMock offers many constraints such as IsEqual(), IsNotEqual(), IsNull(), IsypeOf(), StartsWith(), etc.
For example, the call to set expectations in GetUnitPrice can be done using constraints:
mock.ExpectAndReturn( "GetUnitPrice", 100,
1
);
// Equivalent to ...
mock.ExpectAndReturn( "GetUnitPrice", 100, new IsEqual(1) );
So for SaveBasketItem we can a constraint to instruct the mock object that any GUID value is acceptable:
mock.Expect( "SaveBasketItem", new IsTypeIf(typeof(Guid)), item1 );
Finally, the method Verify() on the mock object can be used to end the test so that NMock verifies that all members were accessed the expected number of times. If a member happens to be accessed fewer times than expected, a VerifyException is thrown.
To summarise then: set the Strict option to true at the beginning, set the required expectations, and then call the Verify method at the end of test.
The Basket class has been designed so that a reference to the data access object can be passed in to the constructor. Usually one would overload the constructor so that you have one constructor in which dependencies can be assigned at run time via the Inversion of Control pattern, and another constructor overload in which these parameters are not included. The first overload would typically be used only for tests, whereas the second overload usually becomes the default and used in production code. So why have this distinction?
With the decoupled approach, units (those that will be tested) communicate with other classes via interfaces. An interface defines methods, properties, indexers and events but does not define constructors. This means that when a class needs to instantiate an interface implementation at run time, it has to make assumptions about the constructor.
The key to creating code that can be unit tested is to practice loos coupling. Loosely coupled code is code that can be easily decomposed into discreet objects / assemblies. If the code is all written as one monolithic ball and you cannot initialize one section of it in isolation from the rest, then your code is tightly and not loosely coupled. Code that is not loosely coupled is difficult to test.
When creating loosely coupled code, it is very helpful to provide interfaces for your key objects. The ideal is to have loosely coupled objects that can be initialized in isolation from one another and that can be accessed through interfaces. A rule of thumb for designing for testability is to ensure that each class performs one, and only one, major function.
A GUI example will help re-enforce the concept of designing classes such that each class performs one, and only one, major function. For example, most dialogs have an OK button that to apply or execute user requests. To properly unit test this code, you need to ensure that data is transferred from the dialog class that contains the OK button to a separate class that holds the data. This ensure that the class dialog only performs one major function; namely getting data from the user. The dialog class should not store the data or perform operations on that data, otherwise it becomes very hard to test.
Separating data operations from user-input operations requires writing extra code - you have to write two classes, one for the input dialog, and another for holding and performing operations on the data. It is important to emphasize that the code that needs to be tested is the code the holds and performs operations on data, and not code that gets data from the user. When was the last time that a textbox failed to retrieve data (all these controls have already been tested by the supplier).
Given that we have agreed to decompose a GUI class into a class for retrieving data and another for holding and performing operations on data, the next question would be how to test the class that holds/performs operations on data? The code that holds/performs operations on data needs a way to obtain this data. To promote loose coupling between this class and the GUI class, a mock object will be used to mock the dialog class. Instead of getting data from the user via the dialog box, you will get data from the mock object. If your classes were not loosely coupled, you would not have been able to substitute a mock object for the dialog input form.
Mock objects are almost always built with interfaces (C# interface keyword). For input dialogs, you typically want to create an interface that can encapsulate the functionality of that input dialog. The following example illustrates:
The following presents the interface requires to capture information from this dialog:
interface IUserDetails
{
string FirstName { get; set;}
string LastName { get; set;}
}
and the User Details dialog box implements the IUserDetails interface as shown:
public partial class UserEntry : Form,
IUserDetails
{
private string strFirstName = String.Empty;
private string strLastName = String.Empty;
public UserEntry()
{
InitializeComponent();
}
#region IUserDetails Members
public string FirstName
{
get { return strFirstName; }
set { strFirstName = value; }
}
public string LastName
{
get { return strLastName; }
set { strLastName = value; }
}
#endregion
}
And finally, the second class that will be used to hold and perform operations on data retrieved from the User Details dialog box:
// The data object. This
is the code that we want to test!
class UserEntryData
{
private IUserDetails m_IUserDetails;
public UserEntryData(IUserDetails i )
{
m_IUserDetails = i;
}
public string FirstName
{
get { return m_IUserDetails.FirstName;
}
}
public string LastName
{
get { return m_IUserDetails.LastName;
}
}
}
The above classes would typically be used as follows:
// Create the User Details
dialog (in a a menu or button command)
UserDetails frmDetails = new UserDetails();
// Display dialog box
frmDetails.ShowDialog( this );
// User has dismissed dialog box. Pass data from the
dialog box into the data class
UserEntryData data = new UserEntryData( frmDetails );
// UserDetails implements IUserDetails
The key point here is that constructor for UserEntryData does not care what it gets passes as long as it implements the IUserDetails interface. And instead of passing a UserEntry which is actually a form and hence difficult to test, we will pass a mock object that supports the IUserDetails interface. Now we can write our testing code, which is very simple:
[TestFixture(Description="Test UserDetails
Form")]
public class UnitTestingGUI
{
[Test]
public void UserName()
{
// Create a
mock object
DynamicMock mock = new
DynamicMock(typeof(GUI.IUserDetails));
// Create the
data object passing the UserDetails dialog mocl
GUI.UserEntryData data = new
GUI.UserEntryData((GUI.IUserDetails)mock.MockInstance);
// Setup
expectations (NMock expects there properties to be called in this order). Values
// returned from these properties are
Yazan and Diranieh, respectively
mock.ExpectAndReturn("FirstName", "Yazan");
mock.ExpectAndReturn("LastName",
"Diranieh");
// Test
Assert.AreEqual(data.FirstName,
"Yazan");
Assert.AreEqual(data.LastName,
"Diranieh");
mock.Verify();
}
}
This section describes a solution to a particular problem that you are most likely to face when creating unit tests. The rules of unit testing specify that you should unit test one class at a time. However, in almost all cases, a given class implementation may depend on other classes. The dependency that concerns us here is that which arises via containment. As discusses previously, the solution is to use mock objects.
Using mock objects works very well if you can pass the contained object to the class under test. However, the situation is more complex if the class you want to test does not expose its dependent classes. Class factories and Inversion of Control come to the rescue.
Again, let's continue with the example of a business object (to be tested) which uses a data access object (a contained object) to communicate with a database as shown below:
public interface IDAL
{
DataSet ExecuteDataSet( string sConn,
string sSQL );
DataReader ExecuteDataReader ( string sConn, string
sSQL );
}
public class SQLServerDAL : IDAL
{
/* IDAL implementation */
}
public class Order
{
private SQLServerDAL obDAL = null;
public Order()
{
obDAL = new SQLServerDAL();
}
...
}
Class Order contains the SQLServerDAL object. This is an example of composition where class Order is completely responsible for the lifetime of class SQLServerDAL. Class Order does not share its instance of SQLServerDAL with any other class nor does it (class Order) provide any properties to expose its SQLServerDAL instance.
In an ideal unit test, only one method of one class should be tested at a time. In the case of class Order, any test you run may inadvertently end up calling methods on class SQLServerDAL. If you want to do integration testing then this is perfectly acceptably. However, for unit testing you should really use a mock of class SQLServerDAL to ensure that you are only testing methods of class Order. For the current implementation of class Order there is no way to pass a mock object.
This solution involves adjusting the design of class Order to accept an instance of class SQLServerDAL. This approach is referred to as inversion of control where the control over the lifetime SQLServerDAL is now outside the control of class Order. Because we defined and implemented interface IDAL for class SQLServerDAL, we can add this new constructor and pass it either a real or a mock instance of SQLServerDAL. The following code illustrates:
public class Order
{
private IDAL obDAL = null;
// This constructor is used for
production code
public Order()
{
obDAL = new SQLServerDAL();
}
// This new constructor can now be used
to pass real of mock instance of SQLServerDAL
public Order(IDAL ob)
{
obDAL = ob
}
...
}
// Pass a real
SQLServerDAL object to class Order
Order o1 = new Order( new SQLServerDAL() );
// Pass a mock instance of
SQLServerDAL to class Order
DynamicMock mock = new DynamicMock(typeof(IDAL));
Order o2 = new Order( (IDAL)mock.MockInstance );
While this is a perfectly reasonable solution, there might be cases when class Order should really control the lifetime of class SQLServerDAL. For example, class Order may need more than one instance of class SQLServerDAL or it might want to create an instance of SQLServerDAL every time it uses a method. The other solution is to pass a class factory.
The term 'class factory' refers to a class whose one and only one purpose is to create classes. The classes created by a class factory often (but not always) have the same type, or derive from a specific class/interface. The following example illustrates:
public interface IDALFactory
{
IDAL CreateDAL();
}
public class RealSQLServerDALFactory :
IDALFactory
{
// IDALFactory implementation
public IDAL CreateDAL()
{
return new SQLServerDAL();
}
}
public class MockSQLServerDALFactory :
IDALFactory
{
// IDALFactory implementation
public IDAL CreateDAL()
{
DynamicMock mock = new DynamicMock(
typeof( IDAL ) );
// Build
expected results
Dataset ds = BuildExpectedDataset();
string sConnectionString =
GetTestConnectionString();
string sSQL = GetTestSQL();
mock.ExepectAndReturn ("ExecuteDataSet",
ds, sConnectionString, sSQL );
return new SQLServerDAL( (IDAL)mock.MockInstance
);
}
}
IDALFactory is a very simple factory interface that will create instances of classes that implement the IDAL interface. RealSQLServerDALFactory an MockSQLServerDALFactory are two implementations of IDALFactory, with the first one returning a real object (i.e., one that would be used in production), and the other returning a mock object (i.e., one that would be used in unit testing).
With these factory implementation, we can change class Order to accept the class factory interface rather than the IDAL interface:
public class Order
{
private IDALFactory obDALFactory = null;
private IDAL
obDAL = null;
// This constructor is used for
production code
public Order()
{
obDAL = new SQLServerDAL();
}
// This new constructor can now be used
to pass a SQLSErverDAL class factory. Whether we create a real
// or a mock object of SQLServerDAL, depends on the run-time
type of the passed-in factory
public Order(IDALFactory ob)
{
obDALFactory = ob;
obDAL = obDALFactory.CreateDAL();
// Can be real or mock. See note above
}
// Now that Order has full control over
the lifetime of SQLServerDAL objects, it can use the factory
// again and again to create instance of IDAL as required
public void AdjustOrder()
{
IDAL ob = obDALFactory.CreateDAL();
}
...
}
// Pass a factory to
create real objects of SQLServerDAL
Order o1 = new Order( new RealSQLServerDALFactory() );
// Pass a factory to
create mock objects of SQLServerDAL
Order o2 = new Order( MockSQLServerDALFactory() );
The following code illustrates the full example:
// Interface to be
implemeted by SQLServerDAL
public interface IDAL
{
DataSet
ExecuteDataSet(string sConn, string sSQL);
SqlDataReader
ExecuteDataReader(string sConn, string sSQL);
}
// Data access class for SQL Server. Implements IDAL
public class SQLServerDAL : IDAL
{
// IDAL implementation
public DataSet ExecuteDataSet(string sConn, string
sSQL)
{
// A very
generic and simple implementation
SqlCommand cmd = new
SqlCommand();
cmd.CommandText = sSQL;
cmd.CommandType = CommandType.Text;
cmd.Connection = new
SqlConnection(sConn);
DataSet ds = new DataSet();
SqlDataAdapter da = new
SqlDataAdapter();
da.Fill(ds);
return ds;
}
public SqlDataReader ExecuteDataReader(string sConn, string
sSQL)
{
// A very
generic and simple implementation
SqlCommand cmd = new
SqlCommand();
cmd.CommandText = sSQL;
cmd.CommandType = CommandType.Text;
cmd.Connection = new
SqlConnection(sConn);
SqlDataReader reader =
cmd.ExecuteReader();
return reader;
}
}
// An improvement over class
SQLServerDAL. It now has a constructor to accept either
// a real object or a mock object
public class Order_Solution1
{
private IDAL obDAL = null;
// This constructor is used for
production code. It creates a real instnace of SQLServerDAL
public Order_Solution1() { obDAL = new SQLServerDAL();
}
// This new constructor can now be used
to pass a real of mock instance of SQLServerDAL.
// Note the inversion of control, where the client of this
class is now responsible for
// creating this class's instnace of SQLServerDAL
public Order_Solution1(IDAL ob) { obDAL = ob; }
// Public interface
public void PlaceOrder()
{
// Collect data
// ...
// Place
order with database. Note that even though obDAL.ExecuteDataSet does not
actually
// gets called because obDAL is a
mock object, the returned dataset ds contains the data that
// was set up in the mock, namely one
table with two rows
string
sConnection = "Database=AdventureWorks;Server=LAPTOP\\SQLEXPRESS;Integrated
Security=SSPI;";
string sSQL = "select * from
HumanResources.JobCandidate";
obDAL.ExecuteDataSet(sConnection,
sSQL);
}
}
public interface IDALFactory
{
IDAL CreateDAL();
}
// Implements IDALFactory by creating a real instance of
SQLServerDAL class
public class RealSQLServerDAL_Factory : IDALFactory
{
// IDALFactory implementation
public IDAL CreateDAL()
{
return new SQLServerDAL();
}
}
// Implements IDALFactory by creating a mock instance of
SQLServerDAL class
public class MockSQLServerDAL_Factory : IDALFactory
{
// IDALFactory implementation
public IDAL CreateDAL()
{
// Setup mock
DataSet dsExpectedData;
string strConnection, strSQL;
DynamicMock mock =
SetupMockForTest1(out dsExpectedData, out strConnection, out strSQL);
// Return mock
instance
return (IDAL)mock.MockInstance;
}
private NMock.DynamicMock SetupMockForTest1(out DataSet ds,
out string s, out string sql)
{
// Setup inputs
adn expected output
s = "Database=AdventureWorks;Server=LAPTOP\\SQLEXPRESS;Integrated
Security=SSPI;";
sql = "select * from
HumanResources.JobCandidate";
ds = new DataSet();
DataTable dt1 = ds.Tables.Add();
dt1.Columns.Add("ID", typeof(int));
dt1.Columns.Add("Name", typeof(string));
dt1.Rows.Add(new object[] { 1, "yazan"
});
dt1.Rows.Add(new object[] { 2, "diranieh"
});
DynamicMock mock = new
DynamicMock(typeof(IDAL));
mock.ExpectAndReturn( "ExecuteDataSet",
ds, new object[] {s, sql } );
return mock;
}
}
public class Order_Solution2
{
private IDALFactory obDALFactory = null;
private IDAL obDAL = null;
// This constructor is used for
production code. It creates a real instnace of SQLServerDAL
public Order_Solution2() { obDAL = new
SQLServerDAL(); }
// This new constructor can now be used
to pass a SQLSErverDAL class factory. Whether we create a real
// or a mock object of SQLServerDAL, depends on the run-time
type of the passed-in factory
public Order_Solution2(IDALFactory ob)
{
obDALFactory = ob;
obDAL = ob.CreateDAL();
}
// Public interface
public void PlaceOrder()
{
// Collect data
// ...
// Place
order with database. Note that even though obDAL.ExecuteDataSet does not
actually
// gets called because obDAL is a
mock object, the returned dataset ds contains the data that
// was set up in the mock, namely one
table with two rows
string
sConnection = "Database=AdventureWorks;Server=LAPTOP\\SQLEXPRESS;Integrated
Security=SSPI;";
string sSQL = "select * from
HumanResources.JobCandidate";
obDAL.ExecuteDataSet(sConnection,
sSQL);
}
public void AdjustOrder()
{
// Note how we
can create new instances of DAL
IDAL o =
obDALFactory.CreateDAL();
// ....
}
}
[TestFixture(Description="Tests class factory")]
public class MainTest
{
private ClassFactory.IDALFactory obDALFactory = null;
[SetUp]
public void Init()
{
obDALFactory = new
MockSQLServerDAL_Factory();
}
[TearDown]
public void DeInit()
{
obDALFactory = null;
}
[Test(Description = "Testing solution1")]
public void TestSolution1()
{
// Create a
mock instnace of SQL Server DAL and pass to class Order
string s = "Database=AdventureWorks;Server=LAPTOP\\SQLEXPRESS;Integrated
Security=SSPI;";
string sql = "select * from
HumanResources.JobCandidate";
DataSet ds = new DataSet();
DataTable dt1 = ds.Tables.Add();
dt1.Columns.Add("ID", typeof(int));
dt1.Columns.Add("Name", typeof(string));
dt1.Rows.Add(new object[] { 1, "yazan"
});
dt1.Rows.Add(new object[] { 2, "diranieh"
});
DynamicMock mock = new
DynamicMock(typeof(IDAL));
mock.ExpectAndReturn( "ExecuteDataSet",
ds, new object[] {s, sql } );
// Run test
Order_Solution1 obOrder = new
Order_Solution1((IDAL)mock.MockInstance);
obOrder.PlaceOrder();
mock.Verify();
}
[Test(Description = "Testing solution2")]
public void TestSolution2()
{
ClassFactory.Order_Solution2 obOrder
= new Order_Solution2(obDALFactory);
obOrder.PlaceOrder();
}
}