July 13, 2020
Hot Topics:

Guicing Up Your Testing

  • By Dick Wall
  • Send Email »
  • More Articles »

Putting Guice in Charge

The other thing you still need to do is get Guice involved with the application setup. All of the pieces are in place now, but Guice hasn't been invoked or hooked in yet.

Here is a great rule that Zorzella and Sasha from Google came up with, and which is a good one to remember:

Guice is the new "new".

In other words, in a Guice application you won't see a lot of new() calls. If you create an object using new, there is no way for Guice to get involved. Instead, you use Guice to create an instance of the object you want. This causes the dependency injection to occur, and recur as deep as necessary to get your wired up system.

Take a look at the test setUp where all this happens:

public void setUp() throws Exception
   Injector myInjector = Guice.createInjector(new TaxRateTestingModule());

   taxRateManager = myInjector.getInstance(TaxRateManager.class);

   // set up a couple of customer IDs with tax rates we can use
   taxRateManager.setTaxRateForCustomer(CUST1_ID, 5.5);
   taxRateManager.setTaxRateForCustomer(CUST2_ID, 6.25);

   // also get an Invoice for testing
   invoice = myInjector.getInstance(Invoice.class);

The key lines here are:

   Injector myInjector = Guice.createInjector(new TaxRateTestingModule());

This creates an injector using the TaxRateTestingModule configuration binding you created (so that the injector knows what binds to what).


   taxRateManager = myInjector.getInstance(TaxRateManager.class);

This line gets you a taxRateManager to use in the test. Note that you ask Guice to get you a taxRateManager to use based on the Interface "TaxRateManager". You don't specify a concrete implementation, but the binding rules you created are used for that instead.

The next two lines set a couple of fake customer ID lookup rates using this taxRateManager instance you have. Because you specified that this should be a singleton, later on in the testing when the TaxRateManager is injected into the Invoice class, it will be the same one with these testing values available for the lookup.

Finally, there is a tricky one in there:

   invoice = myInjector.getInstance(Invoice.class);

You need Guice to give you the invoice to use so that it can inject the TaxRateManager into the Invoice so you can use it. But hang on, you didn't specify a binding for the Invoice class, so how does Guice know what to do?

Guice has a special rule for concrete class definitions. Because Invoice is actually a class, not an interface, Guice can make a pretty good guess that the instance you want is an instance of that class. However, by going through Guice to get this class rather than using new(), Guice has a chance to inject the TaxRateManager for you.

Now you have the invoice set up for testing using your fake (fast) TaxRateManager implementation. The actual test looks pretty similar, but there is one change necessary:

public void getTotalWithTax() {

Customer customer = new Customer(); customer.setCustomerId(CUST2_ID); customer.setCustomerName("FredCo"); invoice.setCustomer(customer);
double expResult = 106.25; // Expected total with tax // add some line items invoice.addItem("Beach Ball", "Beach Ball in Multiple Colors", 37.55); invoice.addItem("Swimsuit", "Fashionable swimsuit, black", 62.45); BigDecimal result = invoice.getTotalWithTax(); assertEquals(expResult, result.doubleValue()); }

The highlighted code is the new stuff. If you remember, you pulled out setting the customer from the constructor into a modifier method. Before you can test the class, you need to set a customer for that invoice. These lines do exactly that.

Why is this necessary? Well, remember that Guice is the new "new". Because you don't call the constructor any more, you can't provide constructor parameters. Instead, you need another way to pass in the information.

Customer, in this case, could have been bound and injected as well, but if you think about it that doesn't really fit. What are the chances that all of the Invoices you create are going to be for the same customer? Heck, even in our little test, the most likely second test will be to test the tax rate calculation for the other customer!

So, using a setter is the easiest way around the problem, but it is definitely sub-optimal. Perhaps the biggest problem is the lack of finality: The customer can be changed on an existing Invoice—that doesn't sound particularly safe—it really should be immutable once set. You could add some code to check whether the customer has already been set for an Invoice and throw an exception if someone tries to reset it, but face it, this is all starting to smell like a bit of a hack.

Also, there is the very real risk that someone creating an Invoice will forget to set the customer on it. Your code is (deliberately) not particularly safe in this circumstance and will fail with a NullPointerException if someone tries to calculate the total with tax when the Customer has not been specified.

Page 4 of 5

This article was originally published on June 21, 2007

Enterprise Development Update

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

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