Contents
With over 35,000 downloads of Nunit Framework per month (see here), there is a noticeable trend that Test Driven Development and the values of having a set of Automated Tests are worth the price. Even Microsoft has joined the game and is distributing its own testing framework (we will actually use this framework in the examples of this article). There are many articles about Test Driven Development, but once you start writing tests, you will notice that you start to design your code to become testable. In this article, we will see that these techniques used to create testable code, unnecessarily make your code complex and thus hard to maintain. This is something which is opposite to the Test Driven Way of Creating the Simplest thing that will work.
The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. Although this goal is easy to understand, it is quite hard to implement. This is where Mocks and Mocking Frameworks come in handy. To see a good explanation, see Mark Seemann's Mock Objects to the Rescue! Test Your .NET Code with NMock article.
The basic idea of mocks is to intercept the normal flow of the code and to insert dummy objects. These objects are not so dumb, and their behavior can be programmed and verified; this includes verifying the arguments passed, returning different values, and validating that the expected calls where actually made. All this is fine and will work, but it leaves us with a problem: in order to intercept the normal flow, we are going to have to somehow change the real objects with mock ones.
The normal way to do this is to change the design of your program to allow object swapping. We will see that this leads to creating virtual methods or new interfaces just for testing.
Let's see an example: we are creating an ATM, we are going to implement a method that performs the withdrawal of our cash.
Here is one way to implement this.
namespace ATM
{
public class Teller
{
public bool Withdraw(Account account, decimal amount)
{
Transaction transaction = new Transaction(account, -amount);
transaction.Execute();
if (transaction.IsSuccessful)
{
Dispose(amount);
return true;
}
return false;
}
}
}
This looks simple and easy to understand, but can we test this in Isolation? How do we isolate Transaction
?
The first thing that we are going to do is to write our mock Transaction
, one that doesn't really access a database. To do so, we are going to create an interface for Transaction
and create our own MockTransaction
. In the following example, we will create a manual mock, but you can use any mock framework.
using System;
namespace ATM
{
public interface ITransaction
{
void Execute();
bool IsSuccessful { get; }
}
}
And our MockTransaction
:
using ATM;
namespace ATM.Test
{
public class MockTransaction : ITransaction
{
public Account account;
public decimal amount;
public int executeCalledCounter = 0;
public MockTransaction(Account account, decimal amount)
{
this.account = account;
this.amount = amount;
}
#region ITransaction Members
public void Execute()
{
executeCalledCounter++;
}
public bool IsSuccessful
{
get { return true; }
}
#endregion
}
}
If we could only swap the creation of Transaction
to MockTransaction
in Teller.Withdraw()
, we could then run our test and verify that our code works well when the transaction succeeds. The techniques that we are going to see are part of Dependency Injection or Inversion of Control Techniques.
This is the simplest way to isolate a Transaction
. We extract the creation command to a virtual method, and we override this method for the test. Here is what our code now looks like. Notice the use of ITransaction
and the CreateTransaction
virtual method.
namespace ATM
{
public class Teller
{
public bool Withdraw(Account account, decimal amount)
{
ITransaction transaction = CreateTransaction(account, -amount);
transaction.Execute();
if (transaction.IsSuccessful)
{
Dispose(amount);
return true;
}
return false;
}
protected virtual ITransaction CreateTransaction(Account account,
decimal amount)
{
return new Transaction(account, amount);
}
}
}
In order to inject our own MockTransaction
, we are going to have to create a new class that is derived from Teller
. In this class, we will override the creation method and swap it with our own MockTransaction
. Here is our test:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
namespace ATM.Test
{
public class TellerForTest : Teller
{
public MockTransaction mockTransaction;
protected override ITransaction CreateTransaction(Account account,
decimal amount)
{
this.mockTransaction = new MockTransaction(account,amount);
return this.mockTransaction;
}
}
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
TellerForTest teller = new TellerForTest();
Account account = new Account();
bool actual = teller.Withdraw(account,100);
Assert.AreEqual(true, actual);
Assert.IsNotNull(teller.mockTransaction);
Assert.AreSame(account, teller.mockTransaction.account);
Assert.AreEqual(-100, teller.mockTransaction.amount);
Assert.AreEqual(1, teller.mockTransaction.executeCalledCounter);
}
}
}
For this pattern, we created one interface and one virtual method in our production code, and one mock object and one derived class for our tests. This method works well when we have one place where we create a Transaction
, but if we create a transaction in multiple places, we are going to have code duplication (many CreateTransaction
methods). A better way in this scenario is to use an Abstract Factory.
In order to have a better way to insert mock objects, we can use a variation of an Abstract Factory. Here is what our code will look like. We will have some creator classes that actually create the ITransaction
, and a Factory that will call the correct Creator
and return our newly made ITransaction
. The Factory with a method to change the creator may look as follows:
namespace ATM
{
public class TransactionFactory
{
static ITransactionCreator creator;
public static ITransactionCreator Creator
{
set { creator = value; }
}
public static ITransaction CreateTransaction(Account account,
decimal amount)
{
return creator.Create(account, -amount);
}
}
}
Our creator interface:
namespace ATM
{
public interface ITransactionCreator
{
ITransaction Create(Account account, decimal amount);
}
}
We will have a default creator:
namespace ATM
{
internal class TransactionCreator : ITransactionCreator
{
#region ITransactionCreator Members
public ITransaction Create(Account account, decimal amount)
{
return new Transaction(account, amount);
}
#endregion
}
}
And for our test, we need a MockCreator
:
using ATM;
namespace ATM.Test
{
public class MockTransactionCreator : ITransactionCreator
{
public MockTransaction transaction;
#region ITransactionCreator Members
public ITransaction Create(Account account, decimal amount)
{
transaction = new MockTransaction(account,amount);
return transaction;
}
#endregion
}
}
Our code will now call the factory. Notice the TransactionFactory.CreateTransaction
call.
namespace ATM
{
public class Teller
{
public bool Withdraw(Account account, decimal amount)
{
ITransaction transaction =
TransactionFactory.CreateTransaction(account, -amount);
transaction.Execute();
if (transaction.IsSuccessful)
{
Dispose(amount);
return true;
}
return false;
}
}
}
Our test will now tell our Factory to use the MockCreator
. Note that we now use the normal Teller
.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
namespace ATM.Test
{
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
Teller teller = new Teller();
Account account = new Account();
MockTransactionCreator creator = new MockTransactionCreator();
TransactionFactory.Creator = creator ;
bool actual = teller.Withdraw(account,100);
Assert.AreEqual(true, actual);
Assert.IsNotNull(creator.transaction);
Assert.AreSame(account, creator.transaction.account);
Assert.AreEqual(-100, creator.transaction.amount);
Assert.AreEqual(1, creator.transaction.executeCalledCounter);
}
}
}
Although we have done the tests, we have not finished yet because our TransactionFactory
doesn't know to use the TransactionCreator
by default, but I will leave this as an exercise for the readers.
For this pattern, we created one Factory, two interfaces, one Creator
class, and one property used only for tests (TransactionFactory.Creator
needed to swap the creator class) in our production code, and one mock object and creator for our tests. (Some implementations will require an additional configuration needed to load the default TransactionCreator
.)
Using the methods explained above, we can test our code plus we get the added value of our code having a low correlation, which means that we can change the Transaction
class easily. There is a price to having this amount of flexibility. Let's see what the price is for this.
We now have many interfaces that are exact representations of the concrete class, and changes are required in both places. We have also changed our implementation and our design. This means 3-8 more classes! We have actually created a framework to help us isolate our code. This is not exactly YAGNI (You Ain't Gonna Need It), a practice that is part of Test Driven Development that says we should Do the Simplest Thing to make the tests work. Using an Abstract Factory will lead to more bugs, and will make the code harder to understand, harder to debug, and harder to maintain, all this for a feature that we need only for testing. There must be a better way. Our business features should drive us to create more complex code, not our tests.
But then, how can we isolate the Transaction
class? How do we swap the creation to create a mocked object?
Here, some modern tools come very handy.
This is where TypeMock.NET comes to the rescue. TypeMock has come with a new idea: instead of using POJO and OO ways to isolate our code, we should use other techniques that will enable us to to isolate our code. Using the magic of TypeMock.NET, we can now isolate and swap our Transaction
class without changing our design at all. All we do is tell the framework that when a new Transaction()
is called, swap it with a mocked instance.
Here is an example that will isolate the Transaction
and call the mocked code.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
using TypeMock;
namespace ATM.Test
{
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
Account account = new Account();
using (RecordExpectations record = RecorderManager.StartRecording())
{
Transaction mockTransaction = new Transaction(account, -100);
record.CheckArguments();
mockTransaction.Execute();
record.ExpectAndReturn(mockTransaction.IsSuccessful, true);
}
Teller teller = new Teller();
bool actual = teller.Withdraw(account, 100);
Assert.AreEqual(true, actual);
MockManager.Verify();
}
[TestCleanup]
public void MyTestCleanup()
{
MockManager.CleanUp();
}
}
}
Explanation:
- While we are in the using block, all methods will be recorded and mocked.
- We create a Transaction, meaning that we will mock the next Transaction that will be created.
- We also validate that the arguments passed to the constructor are our account and that we are removing $100 from the account.
- As we don't want to actually execute the transaction we mock the Execute method and the
IsSuccessful
property.
- In this test we will check that our code works OK for successful transactions.
The attentive reader might have noticed that we don't pass any mocked object to the code that we are testing, this is because we don't need to. TypeMock.NET will automatically load the mocked object when needed.
The best part is that our production code hasn't changes and is simple to understand while we are able to test it simply.
But this is only part of the story. Suppose we cannot create our Transaction (the constructor is private). How do we isolate it?
Here Reflective Mocks come handy.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
namespace ATM.Test
{
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
Account account = new Account();
Mock transactionMock = MockManager.Mock(typeof(Transaction));
transactionMock.ExpectConstructor().Args(account, -100);
transactionMock.ExpectCall("Execute");
transactionMock.ExpectGet("IsSuccessful", true);
Teller teller = new Teller();
bool actual = teller.Withdraw(account, 100);
Assert.AreEqual(true, actual);
MockManager.Verify();
}
[TestCleanup()
public void MyTestCleanup()
{
MockManager.CleanUp();
}
}
}
Here we did the same as above but using Reflective Mocks, as these are reflective we pass the method names as strings, this gives us the ability to mock private methods but then we lose the power of intellisense and refactoring.
Using TypeMock.NET we can isolate static methods and sealed classes just as easy a feat that is quite impossible using regular techniques. Suppose that we decided that for the sake of performance we will have a TransactionPool
that will give us Transactions
. Our code will now look as follows:
namespace ATM
{
public class Teller
{
public bool Withdraw(Account account, decimal amount)
{
Transaction transaction =
TransactionPool.GetTransaction(account, -amount);
transaction.Execute();
if (transaction.IsSuccessful)
{
Dispose(amount);
return true;
}
return false;
}
}
}
We can still test this using TypeMock as follows:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
using TypeMock;
namespace ATM.Test
{
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
Account account = new Account();
using (RecordExpectations record = RecorderManager.StartRecording())
{
Transaction mockTransaction =
TransactionPool.GetTransaction(account, -100);
record.CheckArguments();
mockTransaction.Execute();
record.ExpectAndReturn(mockTransaction.IsSuccessful, true);
}
Teller teller = new Teller();
bool actual = teller.Withdraw(account, 100);
Assert.AreEqual(true, actual);
MockManager.Verify();
}
[TestCleanup]
public void MyTestCleanup()
{
MockManager.CleanUp();
}
}
}
TypeMock.NET goes even further and can isolate a chain of calls in one go, a feat that requires many code changes using normal mocking frameworks. Suppose that we decided that our TransactionPool
can be reached from a DataAccess
that is a Singleton. Our code will now look as follows:
namespace ATM
{
public class Teller
{
public bool Withdraw(Account account, decimal amount)
{
Transaction transaction =
DataAccess.Instance.GetTransactionPool().GetTransaction(account,
-amount);
transaction.Execute();
if (transaction.IsSuccessful)
{
Dispose(amount);
return true;
}
return false;
}
}
}
We can still test this using TypeMock as follows:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ATM;
using TypeMock;
namespace ATM.Test
{
[TestClass]
public class TestTeller
{
[TestMethod]
public void TestCanWithdraw()
{
Account account = new Account();
using (RecordExpectations record = RecorderManager.StartRecording())
{
Transaction mockTransaction =
DataAccess.Instance.GetTransactionPool().GetTransaction(account,
-100);
record.CheckArguments();
mockTransaction.Execute();
record.ExpectAndReturn(mockTransaction.IsSuccessful, true);
}
Teller teller = new Teller();
bool actual = teller.Withdraw(account, 100);
Assert.AreEqual(true, actual);
MockManager.Verify();
}
[TestCleanup]
public void MyTestCleanup()
{
MockManager.CleanUp();
}
}
}
As we can see although we have isolated our code, it is still easy to refactor while keeping the tests intact. If at some time our code does require the ability to change the Transaction, for example a customer asked for an ATM that can work with flat files instead of a database we can re-factor our code to use one of the above techniques. In most cases, we won't even have to change our test code.
TypeMock.NET uses Aspect Oriented Programming, a new Methodology that allows mixing classes and joining them in different positions. What TypeMock does is actually isolate the real code and decide when the real code should be run and when it should be mocked. Using the Profiler API, TypeMock.NET monitors the creations and usages of the code and then Instruments the code to enable isolation when needed. To see how this happens, TypeMock.NET supplies a Trace that shows all the instances and methods of mocked types are called.
Although creating interfaces and using Factories is considered a good O/O practice, if taken to the extreme it clutters our code. We should be able to decide what the best design is for the production code without changing it to make our code testable. In the past this was not possible but with modern tools like TypeMock.NET this is feasible, so we should STOP kidding ourselves, there is no need to change our design to make our code testable. The Production Code and features should drive our Design not our tests. This will save us hours of developing and maintaining unneeded code.
I of course would like to discuss this opinion.
Just to clear things up, I am associated with TypeMock.NET but that is only because I really believe in the Tool and the Methodology behind it.
July 30, 2006