Monday, 20 April 2009

Mocking in C#

During my last assignement I worked on refactoring of our project and part of that was to refactor our test. I find out that we use mocks in there. To be more specific we used Rhino Mocks. I was curious how it works, what are advantages/disadvantages and wanted to learn more.

What are mocks (mock objects)

Mock objects are special testing objects which allow developers to test in easier way the behavior of real objects. Classical unit test tests rather the state of objects then their behavior. This is probably the biggest difference between those two test approaches.

For better understanding of differences, there are used and a bit modified examples from Martin Fowler's article. The examples are adapted for .NET environment.

For testing there are used following classes:
IWarehouse interface
public interface IWarehouse
{
int GetInvetory(string name);
void Add(string name, int count);
bool HasInventory(string name, int count);
void Remove(string name, int count);
}
WarehouseImpl class
public class WarehouseImpl : IWarehouse
{
private Dictionary store = new Dictionary();

public int GetInvetory(string name)
{
if (store.ContainsKey(name))
return store[name];
else
return 0;
}

public void Add(string name, int count)
{
if (store.ContainsKey(name))
store[name] = store[name] + count;
else
store.Add(name, count);
}

public bool HasInventory(string name, int count)
{
if (store.ContainsKey(name))
if (store[name] >= count)
return true;
else
return false;
else
return false;
}

public void Remove(string name, int count)
{
store[name] = store[name] - count;
}
}
Order class
public class Order
{
private string name;
private int count;
private bool isFilled;

public bool IsFilled { get { return isFilled; } }

public Order(string n, int c)
{
name = n;
count = c;
isFilled = false;
}

public void Fill(IWarehouse warehouse)
{
if (warehouse.HasInventory(name, count))
{
warehouse.Remove(name, count);
isFilled = true;
}
}
}

Classical (NUnit) test example

This is example how usually NUnit tests are written and as you can see there we do some actions (order.Fill()) and check that results are as expected.

[TestFixture]
public class NUnitTest
{
private IWarehouse warehouse;
private const string TALISKER = "Talisker";
private const string HIGHLAND_PARK = "Highland Park";

[SetUp]
public void Setup()
{
warehouse = new WarehouseImpl();
warehouse.Add(TALISKER, 50);
warehouse.Add(HIGHLAND_PARK, 25);
}

[Test]
public void TestOrderIsFilledIfEnoughInWarehouse()
{
Order order = new Order(TALISKER, 50);
order.Fill(warehouse);
Assert.IsTrue(order.IsFilled);
Assert.AreEqual(0, warehouse.GetInvetory(TALISKER));
}

[Test]
public void testOrderDoesNotRemoveIfNotEnough()
{
Order order = new Order(TALISKER, 51);
order.Fill(warehouse);
Assert.IsFalse(order.IsFilled);
Assert.AreEqual(50, warehouse.GetInvetory(TALISKER));
}
}

Rhino Mock example

This code shows how to use Rhino.Mocks to test behavior is as expected.

[TestFixture]
public class RhinoTest
{
private const String TALISKER = "Talisker";

[Test]
public void TestFillingRemovesInventoryIfInStock()
{
Order order = new Order(TALISKER, 50);
MockRepository mock = new MockRepository();
IWarehouse warehouseMock = mock.CreateMock();

Expect.Call(warehouseMock.HasInventory(TALISKER, 50)).Return(true).Repeat.Once();
Expect.Call(delegate { warehouseMock.Remove(TALISKER, 50); }).Repeat.Once();

mock.ReplayAll();
order.Fill(warehouseMock);
mock.VerifyAll();
Assert.IsTrue(order.IsFilled);
}

public void TestFillingDoesNotRemoveIfNotEnoughInStock()
{
Order order = new Order(TALISKER, 51);

MockRepository mock = new MockRepository();
IWarehouse warehouseMock = mock.CreateMock();

Expect.Call(warehouseMock.HasInventory(TALISKER, 51)).Return(false).Repeat.Once();

mock.ReplayAll();
order.Fill(warehouseMock);
mock.VerifyAll();
Assert.IsFalse(order.IsFilled);
}
}

Differences between NUnit test and test which uses Rhino.Mocks

As you can see in the examples above, mocking can help you to test easier in cases you do not want your unit tests e.g. access database, send real emails etc. You only test the behavioural of the classes you are insterested in.

References

Mocks aren't stubs by Martin Fowler
NUnit
Rhino.Mocks

No comments: