September 19, 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 »

This last part is the real power of EasyMock. By verifying the easymock instance, you are making sure not only that the right calls were made, but also that all expected calls were made. This way, you make sure that your Invoice class really is calling the TaxRateManager, with the right argument, and calling it exactly one time. Anything else will generate an error at some point in the test—either when too many calls are made to the instance, or when it reaches the verify and there are unmade calls that were expected.

Test this out—comment out the two lines that call getTotalWithTax() and the assert of the correct value right after it. When running, you should see the following failure:

Testcase: getTotalWithTax(org.bldc.guicetestingdemo
                          .InvoiceTest):   FAILED

   Expectation failure on verify:
      getTaxRateForCustomer(org.bldc.guicetestingdemo
         .Customer@749700e7): expected: 1, actual: 0
junit.framework.AssertionFailedError:
   Expectation failure on verify:
      getTaxRateForCustomer(org.bldc.guicetestingdemo
         .Customer@749700e7): expected: 1, actual: 0
   Expectation failure on verify:
      getTaxRateForCustomer(org.bldc.guicetestingdemo
         .Customer@749700e7): expected: 1, actual: 0
         at org.easymock.internal.MocksControl
            .verify(MocksControl.java:71)
         at org.easymock.EasyMock.verify(EasyMock.java:1306)
         at org.bldc.guicetestingdemo.InvoiceTest
            .getTotalWithTax(InvoiceTest.java:69)

It isn't the prettiest error, but it does let you unwind what has gone wrong. The problem is clearly in the getTotalWithTax test case, and it clearly states the problem is with the verification stage—in particular, the getTaxRateForCustomer method, which is expecting one call with "org.bldc.guicetestingdemo.Customer@749700e7" but getting none.

That org.bldc.guicetestingdemo.Customer@749700e7 is a bit of a problem for me as well; it doesn't tell you much of any use really, does it? Adding a toString method to your POJOs ought to be part of your standard coding practice:

@Override
public String toString() {
   return "Customer {ID=" + customerId + ",
                     Name=" + customerName + "}";
}

Now, when you run the test you will see the more useful message:

getTaxRateForCustomer(Customer {ID=43214321, Name=FredCo}):
   expected: 1, actual: 0

Now, you can see that the expected Customer instance is ID 43214321 and name FredCo. Much more useful. You now can debug the code and look for who is dropping the ball when calling TaxRateManager with these details.

I am a huge fan of EasyMock. It makes testing very easy indeed, and much more effective as well. It is not limited to just method calls and returns, but can do a lot more. For example, say you want to simulate an exception being thrown when you try and get the tax rate for a non-existent customer:

@Test
public void getTotalWithTaxNoSuchCustomer() throws Exception {
   System.out.println("getTotalWithTaxNoSuchCustomer");

   Customer customer = new Customer(11111111, "NoSuchCo");
   EasyMock.expect(taxRateManager.getTaxRateForCustomer(customer))
      .andThrow(new NoSuchCustomerException("NoSuchCo"));

   EasyMock.replay(taxRateManager);

   Invoice invoice = invoiceFactory.createInvoiceForCustomer(customer);

   invoice.addItem("Something cool", "Something really cool", 29.95);

   try {
      BigDecimal result = invoice.getTotalWithTax();
      fail("Should have not been able to find the customer");
   }
   catch(NoSuchCustomerException ex) {
      // expected behavior
      assertEquals("NoSuchCo", ex.getMessage());
   }

   EasyMock.verify(taxRateManager);
}

Here, you set up a customer with a bogus ID and name (in truth, because you are using EasyMock, you could even use a valid customer ID and name and still force an exception, but this hurts the readability of the code. Using something like "NoSuchCo" really spells it out for someone reading your test.

Of course, in the real world, you would probably want to tighten up the Invoice class so that it threw an exception if you tried to create an invoice against a non-existent customer as well. This being a simple illustrative article though, I will leave such an exercise to the reader.

Notice that you are still expecting the call to getTaxRateForCustomer with these bogus customer details, but this time you respond with a thrown NoSuchCustomerException (the "andThrow()" method on the EasyMock script). You can use the same trick to simulate all kinds of failures—for example, by using EasyMock, it becomes trivial to simulate a flaky network being used to call a distributed object—throwing the occasional RemoteException to test that your code copes okay is a great way to bulletproof your library. The same goes for simulating transactional exceptions against some class that normally accesses a database and so on.





Page 4 of 5



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel