November 26, 2014
Hot Topics:

Guicing Up Your Testing

  • June 21, 2007
  • By Dick Wall
  • Send Email »
  • More Articles »

Simple Dependency Injection

The simplest solution is to get rid of the reliance on that singleton. To test the invoice class, there is no reason you need a real database to back it (even if it is a fast one). Look at what you need out of the DbTaxRateManager. There are only two methods you need: setTaxRateForCustomer, and getTaxRateForCustomer. These take a customer ID and return a double. That sort of functionality can be supplied by a hashmap just as easily as a database, and it's faster and lighter too.

Producing a class implemented like this that simulates the desired functionality from the database implementation is known as faking the object (rather than mocking the object, which is more akin to providing a script that the mock object is expecting to follow, and then following it). Either way, the point here is to test the Invoice class and its calculation of sales tax on a number of items rather than the TaxRateManager, so if you assume that the TaxRateManager just stores and retrieves sales tax percentages based on a customer ID, you end up with something like this:

public class FakeTaxRateManager implements TaxRateManager {

   private Map<Integer,BigDecimal> taxRates;

   public FakeTaxRateManager() {
      taxRates = new HashMap<Integer, BigDecimal>();
   }

   public void setTaxRateForCustomer(int customerId, BigDecimal taxRate) {
      taxRates.put(customerId, taxRate);
   }

   public void setTaxRateForCustomer(int customerId, double taxRate) {
      this.setTaxRateForCustomer(customerId, new BigDecimal(taxRate));
   }

   public BigDecimal getTaxRateForCustomer(int customerId) {
      BigDecimal taxRate = taxRates.get(customerId);
      if (taxRate == null) taxRate = new BigDecimal(0;
      return taxRate;
   }
}

There are a couple of things to note here. First, this is a really simple class. More importantly, it stores everything in memory, never going near a database, so it is going to be really fast to use in a unit test (of course, nothing will be stored across tests, but you can set up everything you need in the unit test and in fact you should—unit tests should not rely on assumed state from some other test or run because that state can always be wrong).

The second thing to notice is that your new FakeTaxRateManager implements an interface called TaxRateManager. For your dependency injection to work, you need to create an interface that the Invoice class uses, rather than a specific implementation. The interface can be refactored out by most modern IDEs with just a few clicks, and should look something like this:

public interface TaxRateManager {

   public void setTaxRateForCustomer(int customerId, BigDecimal taxRate);
   public void setTaxRateForCustomer(int customerId, double taxRate);
   public BigDecimal getTaxRateForCustomer(int customerId);

}

Now, you just run through the Invoice class and replace references to DbTaxRateManager with references to the TaxRateManager interface.

Doing this brings all sorts of benefits beyond easy testability, and I will cover some of these in future articles, but in a nutshell, what you have done here is to eliminate an implementation dependency. Invoice no longer relies on a class that uses the Database. Instead it uses an interface, the implementation of which you don't care about as long as it fulfills the contract.

Of course, you can't run against an interface—somewhere along the way you need to have a specific implementation for TaxRateManager that does the work. For real life usage, that will be the DbTaxRateManager implementation, but for your testing you want to use the FakeTaxRateManager instead.

The easiest thing to do (without using something like Guice anyway) is to just pass the TaxRateManager implementation you want to use for the Invoice in to the constructor whenever you create a new Invoice object, something like this:

private final Customer customer;
private List<Item> lineItems;
private final TaxRateManager taxRateManager;

/** Creates a new instance of Invoice */
public Invoice(Customer customer, TaxRateManager taxRateManager) {
   this.customer = customer;
   this.taxRateManager = taxRateManager;
   // set up the line item list
   this.lineItems = new ArrayList<Item>();
}

So now, when you create an invoice, in the real life situation you can pass in the database implementation of the TaxRateManager, and for testing you can pass in your fake one that is much faster.

If you can understand the above concept, you know all you need to know to grasp dependency injection. It's just a fancy way of saying "I will tell you what to use when I configure you".

So, if that's all there is to dependency injection, why do you need something like Guice?

Well, think about this. If you use this trick everywhere, in most instances of most classes in your application, what will that do to your code overall? For one thing, you are going to be doing a lot of passing around of injectable items throughout the application. When I want to make an Invoice somewhere in my application, I need a TaxRateManager implementation to pass in. I can either go and find one somehow (which is likely to cause the same kind of inflexibility as looking it up from within the Invoice class itself), or I can pass the TaxRateManager implementation I want to use in to the code that creates the Invoice, which means that needs to be either looked up or passed in from somewhere else, and so on. Pretty soon, all of our code is going to be dominated by loads of parameters being passed into constructors or method calls all over the place, and the code is going to get ugly.

Also, although the singleton implementation that most people use in Java applications is a nuisance, the concept of re-using a single TaxRateManager rather than creating a brand new one for each Invoice is still probably a good one. Barring concerns about thread safety or being a performance bottleneck, it is probably wasteful of memory (not to mention database connections in the case of the database implementation) to have a new DbTaxRateManager for every Invoice. You could pass around the exact same instance among all of the constructor and method calls, but no one can drop the ball or you will end up with multiple instances.

So, although passing in parameters solves one problem, that of concrete dependencies between class implementations, it introduces a lot more and is not really practical in a large scale application. This is why other patterns such as factories, directories, and so on have become popular; they provide you with some organized way to find and use an object instance that you require.

The problem with directories and factories is that they are heavy and usually a little complicated to set up. They are great in large, enterprise systems, but might be too heavy or bulky for small and light applications. Most of them use string naming lookup too, which carries its own risks (strings that are incorrect can't be caught by a compiler and will only show up at run time).

What you need is some easy and light way to "wire up" all of these inter-object dependencies at runtime. You should be able to specify the intent of what implementation is used under what circumstances in a configuration somewhere, and then let some library do the work of getting everything hooked up, be it for testing or a production run.





Page 2 of 5



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel