October 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Squeezing More Guice from Your Tests with EasyMock

  • July 12, 2007
  • By Dick Wall
  • Send Email »
  • More Articles »

One other thing was pointed out by several reviewers of my first article and I am in total agreement. Instead of TaxRateManager taking a Customer ID, it makes more sense for it to take a Customer instance and return the tax rate based on that instance. This makes the API a little cleaner.

For this, you need to update the TaxRateManager interface, and the fake object implementation of it.

public interface TaxRateManager {

   public void setTaxRateForCustomer(Customer customer,
                                     BigDecimal taxRate);
   public void setTaxRateForCustomer(Customer customer,
                                     double taxRate);
   public BigDecimal getTaxRateForCustomer(Customer customer);

}

And the new Implementation:

@Singleton
public class FakeTaxRateManager implements TaxRateManager {

   ....

   public void setTaxRateForCustomer(Customer customer,
                                     BigDecimal taxRate) {
      taxRates.put(customer.getCustomerId(), taxRate);
   }

   public void setTaxRateForCustomer(Customer customer,
                                     double taxRate) {
      this.setTaxRateForCustomer(customer, new BigDecimal(taxRate));
   }

   public BigDecimal getTaxRateForCustomer(Customer customer) {
      BigDecimal taxRate = taxRates.get(customer.getCustomerId());
      if (taxRate == null) taxRate = new BigDecimal(0);
      return taxRate;
   }

}

It's pretty minor stuff, but it creates a cleaner API to use the TaxRateManager now. The Invoice getTotalWithTax method now has to pass in the Customer rather than the ID:

public BigDecimal getTotalWithTax() {
   BigDecimal total = this.getTotal();
   BigDecimal taxRate =
      this.taxRateManager.getTaxRateForCustomer(customer);
   BigDecimal multiplier = taxRate.divide(new BigDecimal(100));
   return total.multiply(multiplier.add(new BigDecimal(1)));
}

AssistedInject

Mixing the factory pattern with your dependency injection has solved the style issues you introduced in the first article by using Guice, so you might be asking yourself why Guice doesn't offer something to do this for us because this would seem to be a fairly common situation.

Well, Guice actually might offer it soon. Jesse Wilson and Jerome Mourits, a pair of engineers at Google, have created a Guice add-on called AssistedInject which formalizes the use of the factory pattern described above and makes it more Guicy as well. You can download and use the extension now, and a description of it is available on the Guice Google group. It it also going to be submitted into the core Guice project for future inclusion.

Recap

So, that's pretty much it. You can download the source code in the form of a NetBeans project that has been adapted to use both Guice and the factory pattern. You have corrected many of the style issues introduced in the first article when you added Guice to the application. What you should take away from this is that Dependency Injection, although very useful, is only one tool in the toolbox. It can be mixed with other software development practices and design patterns where appropriate, and where it makes sense. Used correctly, it can make your implementation and architecture more beautiful. If that is not the case, you are probably mis-using Guice and you should look for another, cleaner way of achieving the same thing (like in this case—using Guice to inject into a factory class instead, and then using the factory to create instances with immutable properties).

Testing Both Sides of Your Class

Looking at what you have so far, are you doing a good job of testing your Invoice class? Probably not; one test under ideal conditions is not very exhaustive. You get some points for adding a couple of different items to the Invoice and then asking for the total with tax—forcing it to sum up the cost of the items and apply the tax to that sum, but you are only testing one possible customer so you should probably make another call with a different customer and resulting tax rate, and ensure that the total is correct for that as well.

What about customers that don't exist? Your implementation of the FakeTaxRateManager returns a 0 for the tax rate in this case—in other words, it fails silently. In a production system, this is probably not what you want; throwing an exception is probably a better idea.

Okay, say you add a test for another customer that exists with a different tax rate, and check the total for that is different from the first customer. Then, you add another test for a non-existent customer and expect an exception to be thrown. Are you well covered then?

I would like to also make sure that these Invoice instances don't interfere with each other, so a test to add items to different Invoices with different Customers to make sure there is no effect from adding items in one invoice to the total in the other seems like a good idea too.

All of this sounds very exhaustive—surely you have tested your little class well if we do all of this? At least, that is what you would think if you were only used to looking at the one side of your class, the API calls (or if you like, the input to your class). There is another side though, what this class calls—in particular, the calls it makes to the TaxRateManager.

You can tell that the calls are probably pretty near correct because you are getting back the amounts you are expecting, but suppose Invoice is very inefficient and calls the TaxRateManager ten times instead of one to try and get the answer? In a system that needs to be highly performant, that is unacceptable overhead. You want to make sure that, for a given call to getTotalWithTax, only one lookup call is made, for the correct customer, to the TaxRateManager.





Page 2 of 5



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel