« How to write the best exception constructors | Main | Difficulty Testing With Static Objects »
Tuesday
Aug112009

Using Package-private constructor to Test With Static Objects

In my previous post, I explained the difficulty of testing with static objects. This post shows how to use a package-private constructor to deal with that problem.

Here's the class we need to fix:

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

  public Foo()
  {
    preferences = Preferences.systemNodeForPackage(SomeOtherClass.class);
  }

  public void disable()
  {
    preferences.putBoolean(KEY, false);
  }
  
  public void enable()
  {
    preferences.putBoolean(KEY, true);
  }
 
  public boolean isEnabled()
  {
    final boolean defaultValue = false;
    return this.preferences.getBoolean(KEY, defaultValue);
  }
}
This class needs fixing because it's hard to write independent tests because each Foo instance uses the same Preferences object. We fix the problem by introducing a package-private constructor:
final class Foo
{
  private static final String KEY = "SOME_KEY";
  private final Preferences preferences;

  public Foo()
  {
    this(Preferences.systemNodeForPackage(Foo.class));
  }

  Foo(final Preferences preferences)
  {
    this.preferences = preferences;
  }

  public void enable()
  {
    preferences.putBoolean(KEY, true);
  }

  public void disable()
  {
    preferences.putBoolean(KEY, false);
  }

  public boolean isEnabled()
  {
    final boolean defaultValue = false;
    return this.preferences.getBoolean(KEY, defaultValue);
  }
}
Note that the package-private constructor does not impact normal consumers of Foo in any way. Consumers continue to use the default constructor that (unbeknown to them) delegates to the package-private constructor.

Rather than use Foo's default constructor, our tests use the package-private constructor and Mockito to inject a mock Preferences object that each Foo instance uses:

public class FooTest extends TestCase
{
private Foo foo;

public void setUp() throws Exception
{
final Preferences preferences = mock(Preferences.class);
this.foo = new Foo(preferences);
}
public void testDisable() throws Exception
{
this.foo.disable();
assertFalse(this.foo.isEnabled());
}
public void testEnable() throws Exception
{
this.foo.enable();
assertTrue(this.foo.isEnabled());
}
public void testConstructor() throws Exception
{
assertFalse(this.foo.isEnabled());
}
}

Now testEnable fails because its Foo instance calls Preferences.putBoolean on a mock. By default, when a void method (such as Preferences.putBoolean) is called on a mock created by Mockito, the mock does nothing. Therefore, we need to change our testing approach.

To test a Foo method, we assert that the Foo instance delegates to its Preferences object. To test the Foo constructor, we assert that no delegation to its Preferences object has occurred:

public class FooTest extends TestCase
{
private Preferences preferences;
private Foo foo;

public void setUp() throws Exception
{
this.preferences = mock(Preferences.class);
this.foo = new Foo(preferences);
}
public void testDisable() throws Exception
{
this.foo.disable();
verify(this.preferences).putBoolean(Foo.KEY, false);
}
public void testEnable() throws Exception
{
this.foo.enable();
verify(this.preferences).putBoolean(Foo.KEY, true);
}
public void testConstructor() throws Exception
{
verifyNoMoreInteractions(this.preferences);
}
}

Note that to test delegation, we changed Foo.KEY from a private member to a package-private member. That's a perfectly natural event that occurs during testing. Treat the test class as a first-class consumer of the production class and let the test class dictate the API of the production class.

PrintView Printer Friendly Version

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>