views:

288

answers:

1

This question about unit testing best practices mentions designing classes for dependency injection. This got me thinking as to what exactly that might mean.

Having just started working with inversion of control containers I have some ideas on the issue, so let me throw them against the wall and see what sticks.

The way I see it, there are three basic types of dependencies that an object can have.

  1. Object Dependency - An actual object that will be used by the class in question. For example LogInVerifier in a LogInFormController. These should be injected in through the constructor. If the class is sufficiently high level that it requires more than 4 of these objects in the constructor consider breaking it up or at the very least using a factory pattern. You should also consider providing the dependency with an interface and coding against the interface.
  2. A Simple Setting - For example a threshold or a timeout period. These should generally have a default value and be set via a builder of factory pattern. You can also provide constructor overloads which set them. However in most cases you probably shouldn't be forcing the client to have to set it up explicitly.
  3. A Message Object - An object that is handed off from one class to another which the receiving class presumably uses for business logic. An example would be a User object for a LogInCompleRouter class. Here I find it is often better for the message not to be specified in the constructor as you would then have to either register the User instance with the IoC Container (making it global) or not instantiate the LogInCompleteRouter until after you have an instance of User (for which you couldn't use DI or at least would need an explicit dependency on the Container). In this case it would be better to pass in the message object in only when you need it for the method call (ie. LoginInCompleteRouter.Route(User u); ).

Also, I should mention that not everything should be DI'ed, if you have a simple bit of functionality that was just convenient to factor out to a throw-away class, it is probably ok to instantiate on the spot. Obviously this is a judgement call; if I found it expedient to write a class such as

class PasswordEqualsVerifier {
  public bool Check(string input, string actual) { return input===actual;}
}

I probably wouldn't bother dependency injecting it and would just have an object instantiate it directly inside a using block. The corollary being that if it is worth writing unit tests for, then it is probably worth injecting.

So what do you guys think? Any additional guidelines or contrasting opinions are welcome.

+1  A: 

The important thing is to try to code to interfaces and the have your classes accept instances of those interfaces rather than create the instances themselves. You can obviously go crazy with this, but it's a general good practice regardless of unit testing or DI.

For example, if you have a Data Access Object, you might be inclined to write a base for all DAOs like this:

public class BaseDAO
{
    public BaseDAO(String connectionURL, 
                   String driverName, 
                   String username, String password)
    {
        // use them to create a connection via JDBC, e.g.
    }

    protected Connection getConnection() { return connection; }
}

However, it would be better to remove this from the class in favor of an interface

public interface DatabaseConnection
{
    Connection getConnection();
}

public class BaseDAO
{
    public BaseDAO(DatabaseConnection dbConnection)
    {
        this.dbConnection = dbConnection;
    }

    protected Connection getConnection() { return dbConnection.getConnection(); }
}

Now, you can provide multilple imlementations of DatabaseConnection. Even ignoring unit testing, if we assume we are using JDBC, there are two ways to get a Connection : a connection pool from the container, or directly via using the driver. Now, your DAO code isn't coupled to either strategy.

For testing, you can make a MockDatabaseConnection that connects to some embedded JDBC implementation with canned data to test your code.

davetron5000