I'm adding unit testing to existing code and encounter this problem:
public class Foo { private static Foo SINGLETON; private static Trouble TROUBLE; private Foo() { } public Foo newInstance() { if (null == SINGLETON) SINGLETON = new Foo(); return SINGLETON; } public Trouble getTrouble() { if (null == TROUBLE) TROUBLE = new Trouble(); return TROUBLE; } public void doSomething() { getTrouble().doSomethingElse(); } }
Callers are expected to write Foo.newInstance().doSomething()
. Fair enough. For unit testing, I want to replace SINGLETON
with a stub or mock object that extends Foo
in setUp()
and put it back to null
in tearDown()
. Foo
is not under test, but classes I am testing call to it and I need to control the interaction.
What about TROUBLE
? It is a complex object with its own behavior, so I need to stub or mock it as well. But here's the rub: I don't have control over its source. It could be a SWIG-generated wrapper for JNI, or from a third-party jar I lack the sources to. And the class looks something like this:
public final class Trouble { public void doSomethingElse() { } }
Oops! No extending for me. what to do? It is time to rely on delegation.
First, extract an interface from Trouble
, say Troubling
, which contains all the public methods in Trouble
. We cannot change Trouble
to implement Troubling
, but I'll overcome that in a moment.
Second, update Foo
and all callers of Foo.getTrouble
to refer to Troubling
and not Trouble
. This decouples them from dependency on Trouble
.
Third, create a new class which implements Troubling
and delegates to Trouble
:
public class NoTrouble implements Troubling { private final Trouble trouble; public NoTrouble(final Trouble trouble) { this.trouble = trouble; } public void doSomethingElse() { trouble.doSomethingElse(); } }
Lastly, update Foo
to use NoTrouble
instead of Trouble
:
public class Foo { private static Foo SINGLETON; private static Troubling TROUBLE; private Foo() { } public Foo newInstance() { if (null == SINGLETON) SINGLETON = new Foo(); return SINGLETON; } public Troubling getTrouble() { if (null == TROUBLE) TROUBLE = new NoTrouble(new Trouble()); return TROUBLE; } public void doSomething() { getTrouble().doSomethingElse(); } }
We're following the ancient dictum, any problem can be solved by introducing an extra level of indirection. Foo
was already delegating doSomething()
to Trouble
; we just replaced that relationship with an extra level of delegation, Foo
to NoTrouble
to Trouble
. Now I can mock or stub NoTrouble
without needing access to Trouble
.
No comments:
Post a Comment