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 Mapproperties = new HashMap ();
public void when(Snippet snippet) {
for (Map.Entryentry : 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.
If the assert fails, wouldn't the cleanup be skipped?
ReplyDeleteWhat is Snippet? (I tried searching, but the search results are less-than-useful).
ReplyDeleteHello Kaleb,
ReplyDeleteThe link to the previous entry has the code for Snippet.
public class Snippet {}
Cheers, Jay
Michael,
ReplyDeleteYes, it would. You can put a finally in there if you're concerned.
Cheers, Jay
You're in a test, so I would just leverage tearDown. E.g.:
ReplyDeleteclass 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.
I'd consider two alternatives:
ReplyDelete1) 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.