Printed from A Developer website designed for Developers

NUnit using NMock Code Review

Code Download

* Based on NUnit version 2.4.8

Define the DataAccess Interface

Define the IShoppingDataAccess interface, which defines any actions that can be performed by the application classes.

public interface IShoppingDataAccess { String GetProductName(Int32 productID); Int32 GetUnitPrice(Int32 productID); List<BasketItem> LoadBasketItems(Guid basketID); void SaveBasketItems(Guid basketID, List<BasketItem> basketItems); }

These actions will usually involve querying or updating one or more databases.

Define the Basket and BasketItem Objects

Define the Basket class, which represents a class to be unit tested.

public class Basket { private List<BasketItem> m_basketItems; private Guid m_basketID; private IShoppingDataAccess m_dataAccessProvider;
public Basket(IShoppingDataAccess dataAccessProvider) { this.m_dataAccessProvider = dataAccessProvider; this.m_basketItems = new List<BasketItem>(); this.m_basketID = Guid.NewGuid(); }
public void AddItem(BasketItem item) { this.m_basketItems.Add(item); } public Decimal CalculateSubTotal() { Decimal subTotal = 0; foreach (BasketItem item in this.m_basketItems) { subTotal += item.GetPrice(); } return (subTotal); } public List<BasketItem> Load() { return (this.m_basketItems); } public void Save() { this.m_dataAccessProvider.SaveBasketItems(this.m_basketID, this.m_basketItems); } }

Define two constructors; first a default constructor with no input parameters and a second constructor accepting the IShoppingDataAccess interface type as an input parameter. The second constructor will be used by the mocking objects when running the unit tests.

The Basket class invokes the LoadBasketItems and SaveBasketItems methods of the IShoppingDataAccess interface.

Define the BasketItem class, which represents a class to be unit tested. Define two constructors; first a default constructor with two input parameters and a second constructor accepting the IShoppingDataAccess interface type as an input parameter. The second constructor will be used by the mocking objects when running the unit tests.

public class BasketItem { private Int32 m_productID; public String ProductName { get; private set; } public Decimal Quantity { get; set; } public Decimal UnitPrice { get; private set; }
private IShoppingDataAccess m_dataAccessProvider;
public BasketItem(Int32 productID, Int32 quantity, IShoppingDataAccess dataAccessProvider) { this.m_dataAccessProvider = dataAccessProvider; this.ProductID = productID; this.Quantity = quantity; }
public Int32 ProductID { get { return this.m_productID; } set { this.m_productID = value; this.UnitPrice = this.m_dataAccessProvider.GetUnitPrice(this.m_productID); this.ProductName = this.m_dataAccessProvider.GetProductName(this.m_productID); } } public Decimal GetPrice() { return (this.UnitPrice * this.Quantity); } }

The BasketItem class invokes the GetProductName and GetUnitPrice methods of the IShoppingDataAccess interface.

Define the Form Test Fixture Class

Define the Test class, which contains all the unit test code to be run against the Basket and BasketItem classes.

[TestFixture] public class TestFormShopping { private Mockery m_mockery = null; private IShoppingDataAccess m_mockDataAccessProvider;
[TestFixtureSetUp] public void FixtureInitialise() { Console.WriteLine("FixtureInitialise"); }
[SetUp] public void TestInitialise() { this.m_mockery = new Mockery(); this.m_mockDataAccessProvider = (IShoppingDataAccess)this.m_mockery.NewMock(typeof(IShoppingDataAccess)); Console.WriteLine("TestInitialise: {0}", (this.m_mockery == null)); }
[Test] public void TestSetReturnValueOfUnitPriceAndProductName() { Expect.Once.On(this.m_mockDataAccessProvider).Method("GetUnitPrice").With(1).Will(Return.Value(99)); Expect.Once.On(this.m_mockDataAccessProvider).Method("GetProductName").With(1).Will(Return.Value("The Moon"));
Console.WriteLine("TestSetReturnValueOfUnitPriceAndProductName: {0}", (this.m_mockDataAccessProvider == null));
BasketItem item = new BasketItem(1, 2, this.m_mockDataAccessProvider); Assert.That(99, SyntaxHelpers.Is.EqualTo(item.UnitPrice)); Assert.That("The Moon", SyntaxHelpers.Is.EqualTo(item.ProductName)); Assert.That(198, SyntaxHelpers.Is.EqualTo(item.GetPrice()));
this.m_mockery.VerifyAllExpectationsHaveBeenMet(); }
[Test] public void TestExplicitExpectAndReturnOfUnitPriceAndProductName() { Basket basket = null; BasketItem item1 = null; BasketItem item2 = null; List<BasketItem> itemsCol = null;
Expect.Once.On(this.m_mockDataAccessProvider).Method("GetUnitPrice").With(1).Will(Return.Value(99)); Expect.Once.On(this.m_mockDataAccessProvider).Method("GetProductName").With(1).Will(Return.Value("The Moon")); Expect.Once.On(this.m_mockDataAccessProvider).Method("GetUnitPrice").With(5).Will(Return.Value(47)); Expect.Once.On(this.m_mockDataAccessProvider).Method("GetProductName").With(5).Will(Return.Value("Love"));
Console.WriteLine("TestExplicitExpectAndReturnOfUnitPriceAndProductName: {0}", (this.m_mockDataAccessProvider == null));
item1 = new BasketItem(1, 2, this.m_mockDataAccessProvider); item2 = new BasketItem(5, 1, this.m_mockDataAccessProvider); itemsCol = new List<BasketItem>() { item1, item2 };
Expect.Once.On(this.m_mockDataAccessProvider).Method("SaveBasketItems").With(Is.TypeOf(typeof(Guid)), itemsCol);
basket = new Basket(this.m_mockDataAccessProvider); basket.AddItem(item1); basket.AddItem(item2); basket.Save();
Decimal subTotal = basket.CalculateSubTotal(); Assert.That(245, SyntaxHelpers.Is.EqualTo(subTotal));
this.m_mockery.VerifyAllExpectationsHaveBeenMet(); }
[TearDown] public void TestTearDown() { this.m_mockery = null; this.m_mockDataAccessProvider = null; Console.WriteLine("TestTearDown: {0}", (this.m_mockery == null)); }
[TestFixtureTearDown] public void FixtureTearDown() { Console.WriteLine("FixtureTearDown"); } }

A mockery object is created, which represents a mock object containing the methods defined in the IShoppingDataAccess interface.

Two tests are defined in this Test class, with appropriate Setup and TearDown methods at both test and class levels. Debug messages are written to the console for illustration purposes only.