Wednesday, June 24, 2009

Java: Method Chain with Snippet

I've noticed a pattern pop up a few times in my Java code in the past 6 months. Maybe it's a decent pattern, or maybe I only have a hammer.

The problem I'm trying to solve is setting some global state, running a test, and ensuring that the global state is set back to it's original (or correct) value. My usual solution to this problem is to remove global state, but not all global states are created equally. The two global states I've been unsuccessful at removing are the current time and system properties.

In my previous post I described how I freeze time using the Joda library. The example code for freezing time was the first time I used a Method Chain with a Snippet. At the time I thought it was an ugly solution, but the best I could come up with.

A few months later I was testing some code that set and read from the system properties. My first tests set the properties and didn't clean up after themselves. This quickly caused trouble, and I found myself turning to Method Chain with Snippet again.

Here's some example code where I verify that setupDir doesn't overwrite a default property:

@Test
public void shouldNotOverrideDir() {
new Temporarily().setProperty("a.dir", "was.preset").when(new Snippet() {{
new Main().setupDir();
assertEquals("was.preset", System.getProperty("a.dir"));
}});
}

And, here's the code for the Temporarily class

public class Temporarily {
private Map properties = new HashMap();

public void when(Snippet snippet) {
for (Map.Entry entry : properties.entrySet()) {
System.setProperty(entry.getKey(), entry.getValue());
}
}

public Temporarily setProperty(String propertyName, String propertyValue) {
if (System.getProperty(propertyName) != null) {
properties.put(propertyName, System.getProperty(propertyName));
}
System.setProperty(propertyName, propertyValue);
return this;
}
}

The code works by setting the desired state for the test, chaining the state cleanup method, and passing the test code as a Snippet to the state cleanup method. The code exploits the fact that Java will execute the first method, then the argument to the chained method, then the chained method.

For the previous example, the 'setProperty' method is executed, then the Snippet is constructed and the initializer is immediately executed, then the 'when' method is executed. The Snippet argument isn't used within the when method; therefore, no state needs to be captured in the Snippet's initializer.

This pattern seems to work well whenever you need to set some state before and after a test runs. However, as I previously mentioned, it's much better if you can simply remove the state dependency from your test.

6 comments:

  1. Michael Niessner1:22 PM

    If the assert fails, wouldn't the cleanup be skipped?

    ReplyDelete
  2. What is Snippet? (I tried searching, but the search results are less-than-useful).

    ReplyDelete
  3. Anonymous2:41 PM

    Hello Kaleb,
    The link to the previous entry has the code for Snippet.

    public class Snippet {}

    Cheers, Jay

    ReplyDelete
  4. Anonymous8:13 PM

    Michael,
    Yes, it would. You can put a finally in there if you're concerned.

    Cheers, Jay

    ReplyDelete
  5. You're in a test, so I would just leverage tearDown. E.g.:

    class AbstractUnitTest {
    private Map original;
    void setPropertyForTest(name, value) {
    original.put(name, System.getProperty(name));
    System.setProperty(name, value);
    }

    void tearDown() {
    // iterate map, put back originals
    }
    }

    If you didn't like the base class, you could make a PropertySetter class that remembered the original values, and then defer it to in the tearDown(), e.g. PropertySetter.putBackOriginals().

    From what I can tell, it accomplishes the same thing, and with less magic.

    ReplyDelete
  6. I'd consider two alternatives:

    1) injecting a clock. As simple as:
    public interface Clock {
    long getCurrentTime();
    }
    public class RealTimeClock implements Clock {
    long getCurrentTime() {
    return System.currentTimeMillis();
    }
    }


    public class FakeClock implements Clock {
    public long currentTime;
    long getCurrentTime() {
    return currentTime;
    }
    // have additional setter/incrementBy methods
    }

    2) try/finally block to clean up global state, if we really really really have to have global state, which we'd rather not.

    ReplyDelete

Note: Only a member of this blog may post a comment.