Best testing practice: property vs constructor injection

Tim Oxley's Avatar

Tim Oxley

03 Dec, 2009 02:37 AM

So until now I've been exclusively using property injection as it seems to be the most prevalent in the examples, but I've found this to the source of much headaches when writing tests, as everytime I create a new instance of each class, I have to do something like:

var eventBus:IEventDispatcher;
var configuration:IConfig;

[Before]
public function setupTests():void {
    eventBus = new EventDispatcher();
    configuration = new TestConfiguration();
}

private function createObject():MyClass {
    var myClass:MyClass = new MyClass();
    myClass.eventDispatcher = eventBus;
    myClass.dependency = new SomeDependency();
    myClass.configuration = configuration;
            return myClass;
}

[Test]
public function testStuff():void {
    var testMe:MyClass = createObject();
    testMe.changeSomeVar();
    Assert.assertTrue("Some var changed", testMe.changeVar);
}

Now the problem with this is that often I forget which dependencies I'm supposed to provide.
I recently discovered I can do automated constructor injection, where you specify the required dependencies in the constructor.

[Inject]
public class MyClass {

    private var dependency:SomeDependency;
    private var eventDispatcher:IEventDispatcher;
    private var configuration:IConfig;

    public function MyClass(dependency:SomeDependency, eventDispatcher:IEventDispatcher, configuration:IConfig) {
        this.dependency = dependency;
        this.eventDispatcher = eventDispatcher;
        this.configuration = configuration;
    }

    ...

}

Then in my tests my creation method becomes a lot shorter.

private function createObject():MyClass {
    var myClass:MyClass = new MyClass(new SomeDependency(), eventBus, configuration);
            return myClass;
}

[Test]
public function testStuff():void {
    var testMe:MyClass = createObject();
    testMe.changeSomeVar();
    Assert.assertTrue("Some var changed", testMe.changeVar);
}

So I guess I just want to confirm my thoughts/sanity that constructor injection is far better in terms of "ease of testability" because it forces/reminds you to provide the dependencies?
Edit: It also allows you to keep the injected dependencies private members. You can't use property injection on a private property.

I guess in ignorance of swift suspenders automated constructor injection I've been converting everything that I'd normally use constructor injection for to property injection, and in light of the testing headaches, I'm going to probably start converting them back.

Any Thoughts?

  1. Support Staff 1 Posted by Joel Hooks on 03 Dec, 2009 05:04 AM

    Joel Hooks's Avatar

    Shaun has a good post about his general preference for property injection on his blog.

    http://shaun.boyblack.co.za/blog/2009/05/01/constructor-injection-v...

    Mediators, by their very nature, are the sketchiest parts of an application: they are tightly coupled both to the application and the view. Commands are coupled to the application. There is very little need to make either of these actors “safe” or “final”, and for flexibility it is wise to leave them “convenient”.

    Proxies [Models] and Services, however, should not be too coupled to the application. They should be the “safest” most “final” classes in the interests of loose coupling and portability. I would start with setter injection and migrate to constructor injection as those components mature.

  2. Support Staff 2 Posted by Joel Hooks on 03 Dec, 2009 05:06 AM

    Joel Hooks's Avatar

    Should also note that most of the examples were written prior to the support for ctor in SwiftSuspenders.

  3. 3 Posted by Tim Oxley on 04 Dec, 2009 12:06 AM

    Tim Oxley's Avatar

    Thanks for that, a good read.
    It's hard to have an original thought around here.

  4. 4 Posted by Tim Oxley on 04 Dec, 2009 03:08 AM

    Tim Oxley's Avatar

    A benefit of property injection with regards to Robotlegs, is you don't have to do ridiculous stuff if you need a named dependency which just happens to be the last constructor parameter:

    [Inject(name="", name="", name="", name="", name="alertParent")]

    So including this and all of the setting that needs to be done in the constructor, there really is a lot of boilerplate code, increasing potential for error.

    You also don't have to work around the bug in the flash player that means SwiftSuspenders has to create one throwaway version of the object in order to access its properties. Hm.

    I think I will go with Shaun's hybrid concept where I use property injection while sketching components together and on the 'business' tier, and constructor injection in the model & mature classes.

  5. Support Staff 5 Posted by Till Schneidere... on 04 Dec, 2009 10:17 AM

    Till Schneidereit's Avatar

    Hey Tim,

    thanks for writing about the problems with ctor injection. I'm also
    bothered by the boilerplate, so I'm curious: Do you think it'd be
    better to include the arguments position in the name parameters
    instead of making them an ordered list? I guess I could add support
    for that to SwiftSuspenders without breaking the current system. (No
    promises, though!)

    Under that proposal, it'd also be possible to use
    [Inject(name4='alertParent')]
    iff no unnumbered "name" argument is given.

  6. 6 Posted by Tim Oxley on 05 Dec, 2009 02:38 AM

    Tim Oxley's Avatar

    Till,

    Sounds great.

    Thinking out loud, some alternatives to possibly make it read a little clearer:

    a dot or some character between the name and the position
    [Inject(name.3="alertParent", name.5="cthulhu")] etc

    or perhaps name, position pairs
    [Inject(name="alertParent", position="3" ,name="cthulhu", position="5")] etc

    The former probably better though. Whatever way you decide, explicitly stating the position will definitely help clean things up :)

  7. Support Staff 7 Posted by Shaun Smith on 07 Dec, 2009 02:21 PM

    Shaun Smith's Avatar

    Hi Tim,

    Regarding your original question: One way to ensure that your dependencies are satisfied when using property injection is to create an injector in your tests and call injector.injectInto(instanceUnderTesr) or even injector.instantiate(ClassUnderTest) during setup. That way an Error will be thrown if any dependencies are unsatisfied.

  8. Support Staff 8 Posted by Shaun Smith on 07 Dec, 2009 03:01 PM

    Shaun Smith's Avatar

    I'm going to mark this issue as Resolved, but if you feel it hasn't been properly addressed, please feel free to re-open it.

  9. Shaun Smith closed this discussion on 07 Dec, 2009 03:01 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac