The problem
You need to use some kind of Dependency Injection (DI for short) to provision some System Under Test (SUT for short) in tests you’re writing, but can’t or don’t want to use a DI framework to do so.
Alternative titles for this post include:
- Dependency Injection without a Dependency Injection framework
- Why misusing the default namespace for tests is evil
- Make code more testable without completely messing it up
I’m not sure whether I have indeed invented a new design pattern here. Read on and let me know.
Don’t expose your privates
Of course, the simplest form of DI is simply passing things to a constructor. But you probably don’t want to code every class to be handed every dependency at construction. And you definitely don’t want to expose the private parts of your implementation through some unnecessary interface – no, you really don’t.
I’ve seen scary examples of what I call ‘keyhole surgery’ on classes, either by abuse of the internal/default namespace to allow poking into otherwise private implementation, or just by making too many things public. Although the internal/default namespace is perceived to retain a sufficient level of encapsulation, it really doesn’t. Anything in the same package can jump all over it, so it’s somewhat of a loose interface. As such, you should also never leave methods unscoped, which will usually compile to the internal/default namespace. You should also really reserve the internal/default namespace for when you are in control of all classes that will exist in the same package, such as when creating utility packages.
Loose interfaces are bad practice, for several reasons:
- Clarity – Anyone using the class will be confused as to the intentions of its interface
- Cleanliness – Code for tests does not belong in your production code
- Stability – A class with a loose interface is open to misuse and a recipe for failure
- Testability – You should test a class through its interface, not its implementation
- Flexibility – It is hard to refactor classes that ‘leak’ implementation detail
The solution
Well, this is one possible solution and ultimately just my suggestion. It’s especially useful when needing to refactor classes with overuse of the internal/default namespace, since you won’t need to change much test or production code. I call it the Dependency Injection by Extension pattern.
How it works
In short, you provide DI helpers as protected methods in your SUT and subclass it from within your test case, as a script-level class, to expose the DI helpers as public and/or use the constructor to do some of the DI legwork – allowing you to inject mocked dependencies for your tests, etc. This has the advantage of not allowing anything else to meddle with your SUT, other than through its interface. And, if anything in your production code wishes to extend your SUT, potentially overriding and messing up the interface too, then that class should have its own tests anyway. Simples!
Example
The following example shows a very basic class (SystemUnderTest) being extended from within its test case (SomeTest) to allow injection and access to its dependencies, for the purpose of verifying its behaviour under given conditions.
class SystemUnderTest { protected var someDependency:Dependency; private var _someThing:Thing; public function SystemUnderTest() { someDependency = new Dependency(); _someThing = new Thing(); } public function doSomething() : void { _someThing.callSomething( someDependency.getSomething() ); } protected function get someThing() : Thing { return _someThing; } }
package { public class SomeTest { private var sut:SystemUnderTestWithDI; private var someThing:Thing; private var mockDependency:Dependency; public function setUp() { mockDependency = mock(Dependency); sut = new SystemUnderTestWithDI(mockDependency); someThing = sut._someThing; } [Test] public function testSomething():void { var testValue:String = 'foo'; given(mockDependency.getSomething()).willReturn(testValue); sut.doSomething(); verify().that(someThing.callSomething(eq(testValue))); } } } class SystemUnderTestWithDI extends SystemUnderTest { public function SystemUnderTestWithDI(dependency:Dependency) { super(); someDependency = dependency; } public function get _someThing():Thing { return someThing; } }
Hopefully it’s self-explanatory. You may also notice that changing classes which had unnecessary internal/default exposure becomes a simple search and replace exercise. Let me know if you’ve heard of this before, if you have any suggested improvements/alternatives or other questions. Happy testing 🙂