Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Don't sweat unit tests

Learn how to easily test external resources

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

To illustrate test-driven development on a range of scenarios, we are going to build some simple moose-monitoring software.

Moose, known as elk in Europe, are the largest members of the deer family. An estimated 2 million moose exist in the world. To better count them, the (fictional) World Organization for Moose, a.k.a. WOM, has hired us to develop moose-monitoring software. WOM wants rangers to be able to track and record the moose they see.

This article assumes you're comfortable working with Java and Ant, Maven or the CLASSPATH, and that you have written at least one or two simple JUnit tests.

A moose. Just like the ones we'll be counting.

Vanilla JUnit

We begin our project by making a Moose class:

 package moose;
import java.util.Date;
public class Moose {
    private Date dateTimeObserved;
    private int age;
    
    public Moose(Date dateTimeObservedParam, int estimatedAge) {
        this.dateTimeObserved = dateTimeObservedParam;
        this.age = estimatedAge;
    }
}



Although moose can live up to 25 years, most moose taken by hunters are much younger, often only 2 or 3 years old. For WOM reports, we want to know if a moose is more than a certain age, so we add method isOlderThan(int).

Its unit test looks like this:

 public void testIsOlderThan() {

Moose moose = new Moose(new Date(), MOOSE_AGE); assertTrue("Moose age "+ MOOSE_AGE +" should of been older than "+ TEST_AGE, moose.isOlderThan(TEST_AGE) ); }


And the method is:

   boolean isOlderThan(int contenderAge) {
        return this.age >= contenderAge;
    } 

This is a plain vanilla JUnit test, the kind usually used in test-driven development examples. Such a cleanly isolated method is rare; usually, we need a resource that's expensive and/or difficult to construct.

Enter mock objects and jMock

The best time for sighting moose is at dawn or dusk, as it is for most animals. WOM wants us to track which ranger recorded the moose sighting and then be able to query Moose via a String getObserverName() method.

Unfortunately the ranger entry comes in the form of a third-party PersonnelUnit class, which is a monster (although not as big as the Alaskan moose, the biggest of all moose) that we can't construct without an LDAP (lightweight directory access protocol) server.

To test getObserverName(), we could start an LDAP server, seed it with data, run our test, then tear the whole thing down. That would be integration testing, not unit testing, and, well, we'd rather write code. Instead, we create a Ranger interface with just the method we need:

   public interface Ranger {
    
        String getName();   
    } 



This gives us our first unit-testing heuristic: Isolate external dependencies behind interfaces.

We need to change the Moose constructor to also take a Ranger. It becomes:

 ... above as before...

private Ranger observer; public Moose( Date dateTimeObservedParam, int estimatedAge, Ranger observedBy) { this.dateTimeObserved = dateTimeObservedParam; this.age = estimatedAge; this.observer = observedBy; }

... below as before ...


Later, we'll make an implementation of Ranger that delegates the PersonnelUnit for inclusion in the final product. For now, all we need is a simple implementation that gives back the name we want—remember we are testing Moose not Ranger.

This type of simple implementation, where we hard code values and/or define method-call expectations, is called a mock object. Mock objects are needed so often during testing that you can select from a range of libraries to create them for you. One of the best is jMock. It uses the dynamic proxies introduced in J2SE 1.3 to let us create interface implementations at runtime.

To use jMock, we must first change our test to extend MockObjectTestCase, and call the superclass in setup() and teardown():

 public class TestMoose extends MockObjectTestCase {

public void setUp() throws Exception { super.setUp(); } public void tearDown() throws Exception { super.tearDown(); } ... the rest as before ...


Using jMock, the test for getObserverName() is simple:

   public void testObserverName() {
     
        Mock rangerMock = mock(Ranger.class);
        rangerMock.expects( once() ).method("getName").will( returnValue(RANGER_NAME) );
        
        Moose moose = new Moose(new Date(), MOOSE_AGE, (Ranger) rangerMock.proxy() );
        
        assertEquals("Moose did not report correct ranger", RANGER_NAME, moose.getObserverName() );
    } 



Let's run through this line by line:

  •    Mock rangerMock = mock(Ranger.class);
    
    


    This line creates a Mock that pretends to implement the Ranger interface. Calling rangerMock.proxy() returns a Ranger.

  •    rangerMock.expects( once() ).method("getName").will( returnValue(RANGER_NAME) );
    
    


    Here is our juicy line. Its functionality should be obvious: It tells Mock to expect the method getName() to be called exactly once, and, when it is called, it should return the value of RANGER_NAME. At the end of the test, when our tearDown() method calls super.tearDown(), our superclass MockObjectTestCase will check that all expectations were satisfied, and fail the test if they weren't.

  •    Moose moose = new Moose(new Date(), MOOSE_AGE, (Ranger) rangerMock.proxy() );
    
    


    This line creates our moose. Note how we get an implementation of Ranger off the Mock by calling its proxy() method.

  •    assertEquals("Moose did not report correct ranger", RANGER_NAME, moose.getObserverName() ); 
    
    


    Finally, here is the test itself. Easy. Well, it is here, because the Ranger interface is conveniently handed to us, which isn't always the case.



The Registry pattern

Moose like to feed on pondweed. Moose themselves are food for wolves and grizzly bears, and moose calves are preyed upon by black bears. Moose are also vulnerable to a disease carried by white-tailed deer (don't worry, moose are neither a threatened nor endangered species). Because of moose's interactions with other species, WOM has asked us to send a message to other teams whenever a moose is cited. These messages are picked up by rangers studying bears, wolves, deer, and pondweed (moose eat a lot of pondweed, about 25 kg a day in the summer).

The messaging system in our project is an enterprise service bus, such as Tibco Rendezvous, IBM MQSeries, or a JMS (Java Message Service) implementation. Like the PersonnelUnit class that needed an LDAP server, this messaging system requirement could make testing difficult. How can one of these objects exist without a full service bus behind it? How are we going to get a hold of this messaging system? The initial temptation is to pass the buck to the integration testing team (Yeah, right, who has one of those?), but really all we need is an interface and a pattern.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources