Introduction
I was very much impressed about Unit Test at my first look for its easy constructs. I was fascinated by the way a code is tested by means of only a few simple Assert methods.
However, soon I started to feel the heat of this simplicity. Especially, writing unit tests for void methods has never been easy and each time I had written one, I was never quite satisfied with the actual payout of the test.
The following paragraphs will show you one way out of this discomfort through the use of UnitTestContext and mock implementations.

Background
UnitTestContext is a simple static class that can be used as a shared memory among
different classes. A void method of a mock implementation [of an interface] may
utilize this shared memory to have some sort of return mechanism at the same time
as complying to the interface. Then, we can write assertions on the UnitTestContext
to codify our assumptions though unit testing.
An Example void Method
The following is a void method that is intended to be a template method for email
sender classes.
public virtual void Run()
{
try
{
System.Net.Mail.MailMessage message = createEmail(this.EmailAppType);
message = addAttachment(message);
ISMTPClient client = getSMTPClient(this.EmailAppType);
client.Send(message);
}
catch (Exception ex)
{
throw ex;
}
finally
{
OnRunCompleted();
}
}
The problem with this code is, this method is void and it takes no arguments, making it difficult to write Assert calls. Let's take a deeper look into this and find
out an easy and effective approach to unit test this method.
Dependency Injection and use of Interfaces
The above code uses an interface named ISMTPClient to for injecting the dependency on System.Net.Mail.SMTPClient. We will use this interface to create actual and mock
implementations and inject the implementions using Dependency Injection techniques.
public interface ISMTPClient
{
SMTPConfig SmtpConfig
{
get;
set;
}
void Send(MailMessage message);
}
This interface uses the following SMTPConfig class to hold smtp configuration attributes.
public class SMTPConfig
{
public SMTPConfig()
{
}
public SMTPConfig(int id, string name, string host, int port, string userName, string password, bool requiresSSL )
{
_smtpConfigID = id;
_smtpConfigName = name;
_host = host;
_port = port;
_userName = userName;
_password = password;
_requiresSSL = requiresSSL;
}
private int _smtpConfigID;
public int SMTPConfigID
{
get { return _smtpConfigID; }
set { _smtpConfigID = value; }
}
private string _smtpConfigName;
public string SMTPConfigName
{
get { return _smtpConfigName; }
set { _smtpConfigName = value; }
}
private string _host;
public string Host
{
get { return _host; }
set { _host = value; }
}
private int _port;
public int Port
{
get { return _port; }
set { _port = value; }
}
private string _userName;
public string UserName
{
get { return _userName; }
set { _userName = value; }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
private bool _requiresSSL;
public bool RequiresSSL
{
get { return _requiresSSL; }
set { _requiresSSL = value; }
}
}
Now that, we have defined the interface ISMTPClient, we will create two implementations
of this interface. One is SMTPEmailClient that we will be using in our production code. And the other is MockSMTPClient that we will be using for testing purposed
only.
Lets take a look at the SMTPEmailClient implementation first -
internal class SMTPEmailClient :ISMTPClient
{
private SMTPConfig _smtpConfig;
public SMTPConfig SmtpConfig
{
get { return _smtpConfig; }
set { _smtpConfig = value; }
}
private SmtpClient _smtpClient;
public SMTPEmailClient()
{
}
public SMTPEmailClient(SMTPConfig config)
{
_smtpConfig = config;
}
#region ISMTPClient Members
public void Send(System.Net.Mail.MailMessage message)
{
configureClient();
try
{
_smtpClient.Send(message);
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
private void configureClient()
{
if (_smtpConfig == null)
{
throw new Exception("Failed to configure SMTPClient. SmtpConfig is Null. Please provide appropriate value");
}
_smtpClient = new SmtpClient(_smtpConfig.Host, _smtpConfig.Port);
_smtpClient.EnableSsl = _smtpConfig.RequiresSSL;
NetworkCredential credential = new NetworkCredential(_smtpConfig.UserName, _smtpConfig.Password);
_smtpClient.Credentials = credential;
}
}
The template method 'Run()' hits the Send() method of ISMTPClient after it composes
a mail and configures the smtp client. But this void Send() method makes the job of unit
testing much difficult because of the following reasons-
I) Depends on Internet. So, it will not only take time but also fail when there is a problem on the internet communication.
II) This method sends is an email with attachment as an output. But checking the e-mail inbox is not feasible for unit testing because
it may take a while to actually reach the inbox and even worse, the login credentials may not be known for the email recipients.
However, the philosophy of unit test is to test only a unit bunch of code and assume the rest of the world is already tested. So, we would rather want
to test the actual
responsibility of the Run() method. Particulary we can conclude that our Run() method
is performing by checking the following two things -
I) The MailMessage is composed as desired.
II) The SMTPConfig thats passed to the ISMTPConfig contains the desired configuration
values.
So, we write the mock implementaion in the following way to help us testing these
two assumptions-
class MockSMTPClient : ISMTPClient
{
#region ISMTPClient Members
public void Send(System.Net.Mail.MailMessage message)
{
UnitTestContext.Current["Message"] = message;
}
#endregion
#region ISMTPClient Members
public VFSmoothie.Data.SMTPConfig SmtpConfig
{
get
{
return UnitTestContext.Current["SmtpConfig"];
}
set
{
UnitTestContext.Current["SmtpConfig"] = value;
}
}
#endregion
}
Thus, instead of sending the e-mail, this mock implementation just saves the message and
SmtpConfig in the UnitTestContext.Current Dictionary. This removes the dependency
on internet communication and also provides us with the opportunity to write assertions
on the two assumptions about the Run() method. This facilitates us to write effective
unit test for the Run() method.
The UnitTestContext class
Brefore I show you the example test code, please take a look at the following code
for UnitTestContext implementation.
public static class UnitTestContext
{
public static Dictionary<string, object> Current;
static UnitTestContext()
{
Current = new Dictionary<string, object>();
}
public static void Reset()
{
Current = new Dictionary<string, object>();
}
}
The Unit Test Code
Now that, we have all the pieces ready to write unit tests, the following example
lists some assertions on the UnitTestContext to test the void method 'Run()'.
private SomeEmailSender _sender = null;
[SetUp]
public void Init()
{
_sender = new SomeEmailSender();
_sender.EmailClient = new MockSMTPClient();
UnitTestContext.Reset();
}
[Test]
public void TestRun()
{
try
{
_sender.Run();
EmailApp app = new EmailAppDAL().GetEmailApp(EmailAppType.SomeType);
SMTPConfig config = new SMTPConfigDAL().GetSMTPConfig(EmailAppType.SomeType);
List<EmailRecipient> toList = new EmailRecipientDAL().GetEmailRecipients(EmailAppType.SomeType, AddressType.To);
List<EmailRecipient> ccList = new EmailRecipientDAL().GetEmailRecipients(EmailAppType.SomeType, AddressType.CC);
MailMessage message = UnitTestContext.Current["Message"] as MailMessage;
Assert.AreEqual(app.FromEmail, message.From.Address);
Assert.AreEqual(app.FromName, message.From.DisplayName);
Assert.AreEqual(app.Subject, message.Subject);
Assert.AreEqual(app.Body, message.Body);
SMTPConfig smtpConfig = UnitTestContext.Current["SMTPConfig"] as SMTPConfig;
Assert.AreEqual(config.Host, smtpConfig.Host);
Assert.AreEqual(config.Port, smtpConfig.Port);
Assert.AreEqual(config.UserName, smtpConfig.UserName);
Assert.AreEqual(config.Password, smtpConfig.Password);
Assert.AreEqual(config.RequiresSSL, smtpConfig.RequiresSSL);
for(int i = 0; i < message.To.Count; i++)
{
Assert.AreEqual(message.To[i].Address, toList[i].EmailAddress);
}
for (int i = 0; i < message.CC.Count; i++)
{
Assert.AreEqual(message.CC[i].Address, ccList[i].EmailAddress);
}
}
catch (Exception)
{
Assert.Fail();
}
}
This way we can write unit tests for methods that seem unfit for regular unit testing
directions. I hope this will be helpful for someone with similar needs.
Points of Interest
Dependency Injection techniques must be used to actually write effective unit tests. And I have used 'Spring.Net' for
Implementing Dependency Injection. If you haven't yet taken a look at Dependency Injection and wondering about writing
unit tests, I must say, please take a look at this cool technique first.
As a disclaimer, I know there may be other good solutions to similar problem and in that case I will look forward to hear
from my readers.