http://www.developer.com/

Back to article

Writing More Testable Code with Dependency Injection


October 6, 2006

Dependency injection is a simple design pattern that can be used to improve the testability of code by abstracting the dependencies out of a class and transferring control of its dependencies to the client code that uses the class (inversion of control). When I use the term dependency in this article, it can mean any member variable of a class. Take the following class diagram for a PropertyManager class, for example.

Figure 1: PropertyManager's Dependencies

As you can see, the PropertyManager has two private fields, dataManager and logger of type DbPropertyDataManager and Logger, respectively. Since the PropertyManager class uses those instance variables to perform its methods, it can be said they are both dependencies of the PropertyManager class. Presumably, DbPropertyDataManager writes to a database, and Logger writes to some external log file. If I'm unit testing this PropertyManager class, I don't need to perform any of these file or database operations to verify that the PropertyManager works correctly. In fact, I don't want to use the DbPropertyDataManager or Logger classes at all. I want to use a mock or a stub instead. So, it would be better to develop the PropertyManager class such that the dependencies are settable, either through code or an external configuration file. In a nutshell, this is dependency injection: allowing for the specific implementation of a dependency (a class in a "uses" relationship) to be settable (injected).

Dependency injection allows you to better isolate your units when testing your application. External dependencies of a class, such as file, database or network I/O operations, can be abstracted at test time with a different implementation of the dependency.

I'm going to illustrate dependency injection using a Property Management application used to store and retrieve the details of various real estate properties. The PropertyInfo class is a simple data container that stores the details of a particular property.

Figure 2: PropertyInfo Class Diagram

PropertyManager will be used to handle the business logic of the application and DbPropertyDataManager will handle the storing and retrieval of properties from a data store. My initial design calls for PropertyManager to use DbPropertyDataManager to add, update, delete and retrieve PropertyInfo objects from a SQL 2005 database. However, this would lead to some problems during testing. As you may know, database testing can be a pain, and I want to develop this application as painlessly as possible. So, instead of having PropertyManager use DbPropertyDataManager directly, I'll create an interface, IPropertyDataManager that acts as the contract for all of my data related operations (see Listing 1) as well as two separate classes that implement IPropertyDataManager, DbPropertyDataManager and ListPropertyDataManager. DbPropertyDataManager talks to the SQL 2005 database, while ListPropertyDataManager is a simpler implementation that uses an ArrayList as its in-memory data store. This way, during unit testing, I can use the ListPropertyDataManager implementation to isolate the PropertyManager without being dependent on an external SQL 2005 database.

Listing 1: IPropertyDataManager Interface

public interface IPropertyDataManager
{
   int AddProperty(PropertyInfo pi);
   bool DeleteProperty(PropertyInfo pi);
   IList<PropertyInfo> GetAllProperties();
   bool UpdateProperty(PropertyInfo pi);
   int PropertyCount { get; }
}

The next question is, how do I develop the PropertyManager to allow the IPropertyDataManager classes to be injectable through code using the ProperyManager class? According to one of the seminal dependency injection resources, Martin Fowler's article, there are three different techniques to implement dependency injection: constructor injection, interface injection and setter injection. I will demonstrate each of these techniques.

Constructor Injection

The PropertyManager class has two dependencies, an IPropertyDataManager and an ILogger. With the constructor injection technique, I pass the specific IPropertyDataManager and ILogger implementation I want to use to the ProperyManager's constructor when I instantiate a PropertyManager object. Every subsequent call to PropertyManager's methods will use the specific implementation that was passed via the constructor.

Listing 2: Constructor Injection

public class PropertyManager
{
   //the IPropertyDataManager is a dependency of this class...
   private IPropertyDataManager dataManager;
   //the ILogger is another dependency of this class
   private ILogger logger;

   public PropertyManager(IPropertyDataManager dm, ILogger l)
   {
      //and we inject its implementation via the constructor
      this.dataManager = dm;
      this.logger      = l;
   }

   public int Add(PropertyInfo pi)
   {
      //todo: perform some validation
      int pID = this.dataManager.AddProperty(pi);
      this.logger.WriteLog("Added Property " + pID);
      return pID;
   }

   public bool Delete(PropertyInfo pi)
   {
      this.logger.WriteLog("Deleting Property " + pi.ID);
      this.dataManager.DeleteProperty(pi);
   }

   public IList GetPropertyList()
   {
      this.dataManager.GetAllProperties();
   }

   public bool Update(PropertyInfo pi)
   {
      bool didUpdate = this.dataManager.UpdateProperty(pi);
      return didUpdate;
   }
}

Now, I create a factory class that constructs and returns a PropertyManager object based on the context I am working in. If I am performing unit testing, I want a PropertyManager that does not interact directly with the database, and uses the ListPropertyDataManager. When I'm not unit testing, I would want a PropertyManager that uses the DbPropertyDataManager.

Listing 3: PropertyManagerFactory

public class PropertyManagerFactory
{
   public PropertyManager BuildTestPM()
   {
      ILogger logger = new SimpleLogger("simpleLogger");
      IPropertyDataManager pdm = new ListDataProperyManager();
      return new PropertyManager(pdm, logger);
   }

   public PropertyManager BuildDbPM()
   {
      ILogger logger = new SimpleLogger("simpleLogger");
      IPropertyDataManager pdm = new DbDataProperyManager();
      return new PropertyManager(pdm, logger);
   }
}

Now, when I'm writing code to use the PropertyManager class, I use the factory methods above to get the specific instance that I want.

Listing 4: Calling the PropertyManagerFactory

//create the instance to use for unit testing the PropertyManager
//class
PropertyManager unitTestInstance =
   PropertyManagerFactory.BuildTestPM();

//create the instance to use when I want actual DB interaction
PropertyManager dbInstance = PropertyManagerFactory.BuildDbPM();

Setter Injection

In the previous example, we passed the dependencies via constructor to the object that contained the dependencies. With setter injection I set the specific implementation of the dependencies via setter methods. We'll go ahead and follow convention and use C# properties instead of getter and setter methods. We accomplish this technique by creating a property for each dependency of the class. This technique provides some added flexibility over constructor injection, since I can change the implementation of a dependency of a specific instance of a class, without having to create a new instance of that class. However, this technique is somewhat dangerous, since I need to be sure to set the specific implementation of the dependency before calling any methods that rely on any of those dependencies.

Listing 5: Setter Injection using Properties

public class PropertyManager
{
   //the IPropertyDataManager is a dependency of this class...
   private IPropertyDataManager dataManager;
   public IPropertyDataManager DataManager
   {
      get { return dataManager; }
      set { dataManager = value; }
   }

   //the ILogger is another dependency of this class
   private ILogger logger;
   public ILogger Logger
   {
      get { return logger; }
      set { logger = value; }
   }

   public PropertyManager()
   {
   }

   #region Methods that use the dependencies
   public int Add(PropertyInfo pi)
   {
      //todo: perform some validation
      int pID = this.DataManager.AddProperty(pi);
      this.Logger.WriteLog("Added Property " + pID);
      return pID;
   }

   public bool Delete(PropertyInfo pi)
   {
      this.Logger.WriteLog("Deleting Property " + pi.ID);
      return this.DataManager.DeleteProperty(pi);
   }

   public IList<PropertyInfo> GetPropertList()
   {
      return this.DataManager.GetAllProperties();
   }

   public bool Update(PropertyInfo pi)
   {
      bool didUpdate = this.DataManager.UpdateProperty(pi);
      return didUpdate;
   }
   #endregion
}

Client code that uses the PropertyManager now has complete control over its dependencies. Below is an example of client code configuring a PropertyManager by specifying the implementations of its dependencies using setter injection.

Listing 5: Configuring the PropertyManager via Setter Injection

PropertyManager myManager = new PropertyManager();
//use one implementation of DataManager
myManager.DataManager = new DbPropertyDataManager();
myManager.GetPropertyList();

//use a different implementation of DataManager
myManager.DataManager = new ListDataProperyManager();
myManager.GetPropertyList();

Interface Injection

A third technique for dependency injection, interface injection, requires that classes implement an explicit interface. For example, since my PropertyManager has a dependency on both its IPropertyDataManager and its ILogger member variable, I would specify two new interfaces, IPropertyDataManagerInjectable and ILoggerInjectable which would each then be implemented by the PropertyManager class.

Listing 6: Interfaces for Interface Injection

interface ILoggerInjectable
{
   void SetLogger(ILogger l);
}

interface IPropertyDataManagerInjectable
{
   void SetPropertyDataManager(IPropertyDataManager pdm);
}

The implementation of these interfaces simply sets the member variables with the parameter that is passed to the methods of the interface.

Listing 7: Implementing the Interfaces

public class PropertyManager : IPropertyDataManagerInjectable,
   ILoggerInjectable
{
   //the IPropertyDataManager is a dependency of this class...
   private IPropertyDataManager dataManager;
   //the ILogger is another dependency of this cass
   private ILogger logger;

   #region IPropertyDataManagerInjectable Members
   public void SetPropertyDataManager(IPropertyDataManager pdm)
   {
      this.dataManager = pdm;
   }
   #endregion

   #region ILoggerInjectable Members
   public void SetLogger(ILogger l)
   {
      this.logger = l;
   }
   #endregion

   public PropertyManager()
   {
   }
      

//...PropertyManager's methods }

Client code that uses the PropertyManager injects dependencies using the methods specified in the implementation of the interfaces.

Listing 8: Configuring the PropertyManager via Interface Injection

PropertyManager myManager = new PropertyManager();
myManager.SetLogger(new SimpleLogger());
myManager.SetPropertyDataManager(new ListDataProperyManager());
myManager.GetPropertyList();

Possible Enhancements

The provided examples set the different implementation of PropertyManager's dependencies through code. However, with a little effort, you could rig the application to inject its dependencies based on a configuration file. This would increase overall flexibility of the application and would not require recompilation each time the injected dependency is modified. Also, there are several frameworks available that can be used to handle dependency injection for you. A couple frameworks of note are StructureMap and ObjectBuilder, I urge you to investigate them to see if they might meet your needs.

Conclusion

Code that follows the dependency injection design pattern is more testable because the dependencies of a class under test are abstracted outside of that class. This inversion of control decouples the code from external dependencies such as databases, file and network I/O operations that typically inhibit testing. This enables client code that uses the class to specify the actual implementation of the dependencies that they need and leads to code that is more flexible and easier to maintain.

You can download the code here.

About the Author

Dan Gartner is a Senior Consultant with Crowe Chizek and Company, LLC and lives in Chicago, IL. He specializes in designing and developing applications for rapidly evolving organizations using Microsoft technologies.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date