Search code examples
testingc#-4.0dependency-injectiontestdrivendesign

Why extract an interface when we can DI a delegate to make it testable?


I was browsing SO and came across this comment on an answer:

Why do we go through the ceremony of extracting an interface when we can DI a delegate to make it testable?

This articulated a thought I had earlier in the year. Is there anything inherently wrong with injecting pure functionality (function pointers, I guess) instead of interface implementations?


Solution

  • The word 'ceremony' in describing interfaces sounds suspiciously like a comment from a Ruby-ist who anti static-typing. With a good refactoring and templating tool, most of the ceremony goes unnoticed anyway.

    While passing in a delegate has its place, IoC with interfaces is normally easier to implement, use, understand, and maintain.

    Consider a typical constructor:

    public class MyClass
    {
      public MyClass(IAmEasyToUnderstand easy)
      {
        if (easy == null) throw new ArgumentNullException("easy");
      }
    
      public MyClass(Func<bool, int, int, Point> IAmNot, Func<bool> Clear, Action aboutAnything)
      {
        //multiple null checks.
      } 
    }
    
    public interface IAmEasyToUnderstand
    {
       bool DoPointStuff(int a, int b, Point point);
       bool CanHazExecute();
       Action RunRun();
    }
    

    Now consider a class that returns an interface for consumption by another:

    public MyClass
    {
      //IFace
       public IAmEasyToUnderstand FindEasy();
    
      //Not Iface
       Func<bool, int, int, Point> ReturnPointStuffDoer();
       Func<bool> ReturnCanHazExecuteCallback();
       Action WowThisIsAnnoying();
    }
    
    
    var easy = myclassInstance.FindEasy();
    var consumer = new EasyConsumer(easy);
    
    ....
    
    var consumer = new EasyConsumer(
                  myClassInstance.ReturnPointStuffDoer(),
                  myClassInstance.ReturnCanHazExecuteCallback(),
                  myClassInstance.WowThisIsAnnoying());
    

    In looking at latter consumption example, the obvious cleanup would be:

    var consumer = new EasyConsumer(myclassInstance);
    

    which means you need an replace the class type with an interface for mocking.