August 23, 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 »

Recap

In my last Guice article, Guicing Up Your Testing, I explored how Guice and Dependency Injection could be used to substitute faster alternatives to slow resources in your tests to increase performance and reduce reliance on complex or flaky systems.

You took a test that relied on a tax rate lookup system that simulated the use of a slow database. By using Dependency Injection techniques, you eliminated the slow database implementation of the lookup and replaced it with a fake object that just did the lookup using a HashMap implementation.

As you left the first article, you had achieved a couple of orders of magnitude speedup (albeit from a fairly contrived example) but had also taken at least one step backwards in the implementation of your object model.

In the original form, the Invoice Object took a Customer as a constructor argument and hence could make that Customer final and immutable within the invoice. It also used a singleton to obtain the TaxRateManager object that was implemented with the kind of latency that a heavily used database connection might have.

In the Guiced up form, the singleton was eliminated in favor of a TaxRateManager that could be injected. Guice also took care of the creation of the Invoice and TaxRateManager objects as required, but as a result you lost the ability to pass the Customer in as an argument to Invoice, meaning that the field could no longer be final and immutable, but in fact had to be set once the Invoice had been created.

This was all intentional in the first article—where the focus was really on the Dependency Injection approach and how you achieved it with Guice, and I did not want to dilute the message with lots of other considerations like this. However, now is the time to go and put some of that right.

No Silver Bullet

The first thing to recognize is that there is no silver bullet in software development. Just like so many other tools that have preceded it, Guice is staggeringly useful and very neat, but it is not the answer to all problems (not even most of them). I actually like libraries that don't try to be the total solution, but rather concentrate on doing just one thing and doing it really well. In this, Guice is very successful, being a perfect answer to Dependency Injection in Java—it's lightweight, simple and fast.

On the other hand, even the simple testing example highlights some of the limitations. As soon as you lost control of the creation of Invoice, handing that control over to Guice, you lost some control over the immutability of the Customer in the Invoice.

It should be apparent that making Invoice something that Guice creates directly is an incorrect design decision. Instead, you can mix it with another pattern instead: Factory.

A Brief Aside on Design Patterns

The statement about no silver bullets applies, in my opinion, the same to design patterns as it does to libraries such as Guice. Design patterns can encapsulate useful concepts, but over-reliance on them can lead to trouble similar to the trouble you have seen by relying on Guice for everything. Your initial example of the Database-implemented TaxRateManager was slow and difficult to test precisely because you used the Singleton pattern to obtain that TaxRateManager (which made it hard to substitute another implementation in its place).

In the Guice implementation, you did end up using Singleton scope for the TaxRateManager so you could argue (and people do) that the Singleton design pattern is not at fault but rather the implementation is. This is a fair argument, but I still maintain that always thinking in terms of patterns boxes your own approach to development. Design patterns have their place, but so does another favorite of mine: (anti-patterns) and yet another: independent and original thinking.

That said, in this situation there is a strong case for a combination of Guice dependency injection and the Factory design pattern.

You will use Guice to create an InvoiceFactory instance, which will be injected with the TaxRateManager you configure in the Guice binding. You then can use that InvoiceFactory to create new Invoice objects, and which will pass in both the TaxRateManager and a Customer instance that you pass in. That way, both attributes can be made immutable.

Fixing the Cruft

So, you can correct the problems introduced by your previous work to include Dependency Injection. First, you will alter Invoice to take both a Customer and a TaxRateManager in the constructor arguments, and you will also remove the Guice @Inject (because Invoice will not be directly injectable):

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

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

Notice that you have restored the TaxRateManager field to final, and that the constructor takes both the Customer and the TaxRateManager as arguments. You have also pulled out the @Inject.

However, now Guice can't do any injection into this class, so you need to create an InvoiceFactory that will do the work (fortunately it's nice and short):

package org.bldc.guicetestingdemo;

import com.google.inject.Inject;

public class InvoiceFactory {

   private TaxRateManager taxRateManager;

   @Inject
   public InvoiceFactory(TaxRateManager taxRateManager) {
      this.taxRateManager = taxRateManager;
   }

   public Invoice createInvoiceForCustomer(Customer customer) {
      return new Invoice(taxRateManager, customer);
   }

}

There should be no surprises here. Guice is tipped off to @Inject TaxRateManager into the InvoiceFactory constructor. When you are ready to create an Invoice, you invoke the createInvoiceForCustomer method and pass the customer to use in. This then creates a new Invoice using the injected TaxRateManager, and the Customer you desire.

Why use a Factory and not some other design pattern like a Repository here? For what you need right now, a Factory is ample. Your tests do not require a repository to track and look up invoices by some index. Until there is such a need, I am a big believer in keeping it simple, and it doesn't get much simpler than that factory class.

See what that does to your test (just the important changes):

private TaxRateManager taxRateManager;
private InvoiceFactory invoiceFactory;

@Before
public void setUp() throws Exception {

   ....

   invoiceFactory = myInjector.getInstance(InvoiceFactory.class);
}

....

@Test
public void getTotalWithTax() {
   System.out.println("getTotalWithTax");

   Customer customer = new Customer(CUST2_ID, "FredCo");
   Invoice invoice = invoiceFactory.createInvoiceForCustomer(customer);

   ....
}

Instead of getting an Invoice instance in the constructor, you get an instance of InvoiceFactory instead. Then, in the test, InvoiceFactory is used to create an instance of an Invoice using the Customer object. This is much cleaner than the previous implementation.





Page 1 of 5



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel