In a study session at work last week we were having a discussion about how with dependency injection you can end up with loads of anaemic interfaces. Arguably these interfaces provide little value. However in C# at least there’s an alternative (thanks Josh and I think also Joe Campbell for this idea) – instead of taking an interface for a dependency in the client’s constructor, use a delegate instead.
So, instead of this “poor man’s” dependency injection example from my last post:
public interface IUserPaymentRepository
{
void MakeTransaction(Price amount);
}
public class TrackPaymentActivity
{
private UserPaymentRepository _userPaymentRepository;
public TrackPaymentActivity():this(new UserPaymentRepository())
{
}
public TrackPaymentActivity(IUserPaymentRepository userPaymentRepository)
{
this._userPaymentRepository = userPaymentRepository;
}
public AttemptToPayForTrack()
{
......
_userPaymentRepository.MakeTransaction(trackPrice);
......
}
}
you can do this:
public class TrackPaymentActivity
{
private Action _makeTransaction;
public TrackPaymentActivity(Action makeTransaction)
{
this._makeTransaction = _makeTransaction;
}
public AttemptToPayForTrack()
{
......
_makeTransaction(trackPrice);
......
}
}
So how do you test this? Mocking frameworks don’t (and probably couldn’t) support delegates so you’ll need to create an interface which has a method with the signature of the delegate, but only for testing purposes:
internal interface ITestPaymentTransaction
{
void MakeTransaction(Price amount)
}
[Test]
public void Should_Take_Correct_Payment_Amount_For_Track_From_User()
{
IUserPaymentRepository mockedTransaction =
MockRepository.GenererateMock();
new TrackPaymentActivity(mockedTransaction.MakeTransaction)
.AttemptToPayForTrack();
mockedTransaction.AssertWasCalled(transaction => transaction.MakeTransaction(expectedAmount));
}
In most situations I think this is preferable. You’re still having to create an interface, but it’s not creating noise in your production code. It also means you can’t use an IoC container, but as I said in my last post, in many situations you probably don’t need to anyway.