Don't sweat unit tests

Learn how to easily test external resources

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.

Let's hide the messaging system behind a friendly interface:

 

public interface Messenger {

void sendMessage(String topic, Object[] values); }

Here's our second unit-testing heuristic: Interfaces denote services or roles, so their names will often end in -or or -er.

Now, all we need to figure out is how to get an implementation of Messenger, which is easier to locate than moose, who tend to populate edges of big tundra areas or the weedy shorelines of lakes and ponds. If you use an IoC (Inversion of Control) container (such as Spring or HiveMind), you've probably already figured out that the container will hand you the Messenger implementation. Otherwise, use the Registry pattern (a type of Service Locator pattern), which is simply a global static map that maps service names to implementations:

 public class Registry {
 
    private static Map registry = new HashMap();
    
    public static void put(String key, Object implementation) {
        registry.put(key, implementation);
    }
    
    public static Object get(String key) {
        return registry.get(key);
    }
    
} 

In our code, we use Registry like this:

   Messenger messenger = (Messenger) Registry.get("MESSENGER");
    messenger.sendMessage(A_TOPIC, someValues);

Now, let's write our test:

   public void testMessageIsSent() {
    
        Date observationDate = new Date();
        
        Object[] valueArray = new Object[] { observationDate, new Integer(MOOSE_AGE) }; 
        
        Mock messenger = mock(Messenger.class);
        messenger.expects( once() ).method("sendMessage").with( eq(MESSAGE_TOPIC), eq(valueArray) );
    
        Registry.put( "MESSENGER", messenger.proxy() );
        
        Moose moose = new Moose(observationDate, MOOSE_AGE, null );
    } 

Let's run through the important lines:

  •        Mock messenger = mock(Messenger.class);
    
    

    This creates a mock object that implements the Messenger interface. For production code, we would later create an implementation that talked JMS, Rendezvous, or a similar requirement.

  •        messenger.expects( once() ).method("sendMessage").with( eq(MESSAGE_TOPIC), eq(valueArray) );
    
    

    This tells the mock object to expect method sendMessage() to be called exactly once, with two values: a message topic and an array of values.

  •        Registry.put( "MESSENGER", messenger.proxy() );
    
    

    We register the Messenger interface's mock implementation. From now on, all code that gets a Messenger from the registry will receive the mock object we have just created.

  •        Moose moose = new Moose(observationDate, MOOSE_AGE, null);
    
    

    And finally, here's our test. I know this doesn't look like a test, but after it runs, tearDown() will run and call super.tearDown(). That method checks that all expectations have been satisfied, and, if they have not, fails the test. So if the sendMessage() method hasn't been called on our mock messenger, the test will fail. We test without testing!

Zen database testing

I know what you're thinking: "All this talk of LDAP and messaging is very nice, but back in the real world, we have to talk to a database." Well, as our first heuristic dictates (isolate external dependencies behind interfaces), let's simply hide the database behind an interface:

       public interface StorageManager {
            void save(Object objectToSave) throws StorageException;
        }

We put an implementation in the Registry, and our code only has to do this:

       StorageManager storageManager = (StorageManager) Registry.get( Registry.STORAGE );
        storageManager.save( myObject );

The StorageException is our own exception. We would use it to wrap whatever exceptions the implementation throws.

With Hibernate, Java Data Objects, or another good persistence layer, it's easy to abstract yourself away from Java Database Connectivity (JDBC). I know you're bored writing select/insert/update/delete for every object you create. I certainly am. Instead, we could just write a HibernateStorageManager that implements StorageManager and handles the details. (If you really must hand-code JDBC yourself, the Mockrunner project can help you with unit tests.)

In our unit tests, we create a mock of StorageManager and set an expectation that save is called with the correct object. Job done. The test for save looks like this:

 

public void testSave() throws StorageException {

// Create a mock Messenger than ignores any messages it gets Mock messenger = mock(Messenger.class); messenger.stubs().method("sendMessage"); Registry.put( Registry.MESSENGER, messenger.proxy() ); // Create the moose Moose moose = new Moose(new Date(), MOOSE_AGE, null); // Create a mock StorageManager and tell it what will happen when we save the moose Mock storage = mock(StorageManager.class); storage.expects( once() ).method("save").with( same(moose) ); Registry.put(Registry.STORAGE, storage.proxy()); // Test ! moose.save(); }

Let's look more closely at the important lines. First we set up a Messenger stub:

         messenger.stubs().method("sendMessage");

JMock has stubs that are expectations of zero or more invocations. The test won't fail if the stubs are never called or if they are called hundreds of times. We know from the previous section that creating a Moose sends a message via a Messenger interface. This behavior has nothing to do with save, and we do not want it to fail our test, so we set up a stub for it.

Next, we create the Moose object for testing and set up our expectation on the mock StorageManager:

         storage.expects( once() ).method("save").with( same(moose) );

This reads quite naturally. We expect the save method to be called once with our moose object as the single parameter. Internally, the same method uses == to compare objects. The eq() method we used earlier uses equals.

Finally we save the Moose:

         moose.save(); 

As soon as testSave completes, JUnit runs the tearDown() method, which checks that all our expectations were met, and fails the test if they weren't. This test ensures that when we ask a Moose to save itself, it delegates that job to the StorageManager.

1 2 Page 1
Page 1 of 2