Unit-testing – Dependency Injection with default Construction

dependency-injectionunit testing

Most of my production code has fixed types of dependencies, illustrated below: the House for example, at run time, always depends on the same Kitchen.

Therefore, I am wondering what I gain by having the House force whoever constructs it to inject its dependencies as opposed to provide the option to inject the dependencies, i.e. provide both a default constructor and a DI-constructor?

What you lose by not allowing default construction is easily seen if you compare mainDI and mainDefault.
Even if each class has only has two dependencies, if the dependency hierarchy depth is 3, as for example in the House -> Bedroom -> Bed path, you have to instantiate 2^3 objects in your top level class which seems unfeasible for large projects with a dependency hierarchy depth of 10 or more.

Having the DI constructor, I am still able to unit test a class.

So for all the DI experts out there, what is the downside of this approach?

struct House {
  House(Bedroom ib, Kitchen ik) : b(ib), k(ik) {}
  House() : b(Bedroom()), k(Kitchen()) {}
  private:
    Bedroom b;
    Kitchen k;
}

struct Bedroom {
  Bedroom(Bed ib, Lamp il) : b(ib), l(il) {}
  Bedroom() : b(Bed()), l(Lamp()) {}
  private:
    Bed b;
    Lamp l;
}

struct Bed {
  Bed(Frame if, Mattress im) : f(if), m(im) {}
  Bed() : f(Frame()), m(Mattress()) {}
  private:
    Frame f;
    Mattress m;

}


mainDefault() {
  House h()
}


mainDI() {
    Frame f;
    Mattress m;
    Bed b;
    Lamp l;
    Kitchen k;

    Bed b(f, m);
    Bedroom br(b, l);    
    House h(br, k);        
}

Best Answer

Even if each class has only has two dependencies, if the dependency hierarchy depth is 3, as for example in the House -> Bedroom -> Bed path, you have to instantiate 2^3 objects in your top level class which seems unfeasible for large projects with a dependency hierarchy depth of 10 or more.

Make no mistake, you're still instantiating those objects somewhere.

Therefore, I am wondering what I gain by having the House force whoever constructs it to inject its dependencies as opposed to provide the option to inject the dependencies, i.e. provide both a default constructor and a DI-constructor?

You gain visibility. If your main class really is the thing that decides what is instantiated deep into your hierarchy, that's a smell. A huge smell depending on the depth of the hierarchy, since your classes are leaking implementation details all the way down.

By hiding that behind a default constructor (or worse, and IoC container), you're hiding all of that coupling. You should instead work to remove that coupling where it makes sense. You'll get simplified constructors and all of the other benefits of decoupled code.