Difficulty Testing With Static Objects

I was going through some old code the other day improving test coverage when I came across an untested class that looked like this:

public class Foo
{
private static final String KEY = "SOME_KEY";
private final Preferences preferences;

public Foo()
{
this.preferences = Preferences.systemNodeForPackage(
SomeOtherClass.class);
}
public void disable()
{
this.preferences.putBoolean(KEY, false);
}
public void enable()
{
this.preferences.putBoolean(KEY, true);
}
public boolean isEnabled()
{
final boolean defaultValue = false;
return this.preferences.getBoolean(KEY, defaultValue);
}
}

Do you see anything wrong yet?

Nothing looks terribly wrong, but there's a lurking problem - it's hard to test the class because it calls a method that returns a static object. Let me explain by going through the process of adding tests of this class.

My first test method confirms that isEnabled returns false after disable is called:

public class FooTest extends TestCase
{
public void testDisable() throws Exception
{
final Foo foo = new Foo();
foo.disable();
assertFalse(foo.isEnabled());
}
}
There's still apparently nothing wrong. As expected, this test passes, so let's add a test of the enable method:
public class FooTest extends TestCase
{
public void testDisable() throws Exception
{
final Foo foo = new Foo();
foo.disable();
assertFalse(foo.isEnabled());
}
public void testEnable() throws Exception
{
final Foo foo = new Foo();
foo.enable();
assertTrue(foo.isEnabled());
}
}
Everything still works fine. Both tests pass, so let's add a test that confirms that the property defaults to false immediately after construction:
public class FooTest extends TestCase
{
public void testDisable() throws Exception
{
final Foo foo = new Foo();
foo.disable();
assertFalse(foo.isEnabled());
}
public void testEnable() throws Exception
{
final Foo foo = new Foo();
foo.enable();
assertTrue(foo.isEnabled());
}
public void testConstructor() throws Exception
{
final Foo foo = new Foo();
assertFalse(foo.isEnabled());
}
}
Woah - the assertion in testConstructor fails! Even though each Foo instance has its own Preferences member, each member refers to the same Preferences object. Each member gets its value from a static method (Preferences.systemNodeForPackage) that returns the same instance every time it's called by the Foo constructor. This makes our tests lack independence - the call to foo.enable in testEnable impacts the Foo instance in testConstructor.

In my next post, I'll explain how to use a package-private constructor to handle this problem.