Making the untestable testable with Anonymous Methods and Dependency Injection
It can be frustrating to want to write unit tests, only to hit some code which is rather untestable. Take for example, the following code:
public class MailSender
{
public void SendActivationMail(User user, string activationCode)
{
string body = GetCopyOfActivationBody(); //implementation doesn't matter
body = body.Replace("#NAME#", user.Name).Replace("#CODE#", activationKey).Replace("#EMAIL#", user.Email);
var message = new MailMessage("support@piccolo.cool", user.Email, "Account Activation", content) {IsBodyHtml = true};
new SmtpClient().Send(message);
}
}
Obviously, I can't test this method as-is without making sure that a mail server is setup. And even if it was, how would I verify that the properties of my MailMessage object were properly set?
In the past, I probably would have solved this by using a virtual method and mocking it:
public void SendActivationMail(User user, string activationCode)
{
string body = GetCopyOfActivationBody(); //implementation doesn't matter
body = body.Replace("#NAME#", user.Name).Replace("#CODE#", activationKey).Replace("#EMAIL#", user.Email);
var message = new MailMessage("support@piccolo.cool", user.Email, "Account Activation", content) {IsBodyHtml = true};
SendMessage(message);
}
public virtual void SendMessage(MailMessage message)
{
new SmtpClient().Send(message);
}
Now we can create a mock of our MailSender class and mock our SendMessage method. I still think this is an acceptable solution, but I now prefer to inject an anonymous method as a dependency. The benefit is that my unit tests are more straightforward, and I'm not having to expose a method like SendMessage.
Before you move on, you should be familiar with anonymous methods, I've blogged about them in the past and/or you can check out this dimecast.
First we'll change our MailSender class to accept a simple anonymous method into its constructor:
public class MailSender
{
private readonly Action<MailMessage> _sendMessage;
public MailSender(Action<MailMessage> sendMessage)
{
_sendMessage = sendMessage;
}
}
Next we'll change our SendActivationMail to use the sendMessage action:
public void SendActivationMail(User user, string activationCode)
{
string body = GetCopyOfActivationBody(); //implementation doesn't matter
body = body.Replace("#NAME#", user.Name).Replace("#CODE#", activationKey).Replace("#EMAIL#", user.Email);
var message = new MailMessage("support@piccolo.cool", user.Email, "Account Activation", content) {IsBodyHtml = true};
_sendMessage(message);
}
Before we write our test, let's configure this dependency for our normal, production use. Here I'm using Ninject (some version of the 2.0 branch, so the syntax might be slightly different for you):
Bind<Action<MailMessage>>().ToConstant(message => new SmtpClient().Send(message));
That's all there is to it. Now, whenever we get an instance of our MailSender class through Ninject, it'll automatically inject the default behaviour and happily send emails away (for those curious where/how to configure the host/port/username/password, I prefer to do that in the web.config/app.config via the <system.net> section.) As for our tests, they look something like:
[Fact]
public void SendsActivationEmailToUser()
{
var user = new User {Email = "nail@namekian.cool"};
var action = new Action<MailMessage>(m => Assert.Equal(user.Email, m.To.ToString()));
new MailSender(action).SendActivationMail(user, null);
}
There you have it. By injecting our own test-specific anonymous method into our MailSender class we've turned otherwise untestable code into code which is easy to test.
Posted
Fri, May 8 2009 10:42 AM
by
karl