Test isolation: Dependency Injection vs stubbing constructors

To fully isolate the tested unit, we must be able to stub its dependencies. In many programming languages, stubbing hard-coded constructors is impossible. The Dependency Injection pattern is the go-to solution for this problem – it allows us to extract dependencies into parameters provided from the outside of the tested unit, which in turn allows us to substitute them with stubs.

In some languages, though, due to their dynamic nature, stubbing hard-coded constructors IS possible (e.g. in JavaScript and Ruby). Is there still a reason to use Dependency Injection in such languages? Or is it just an unnecessary complication?

Dependency Injection example

// RandomNumber's behavior is non-deterministic - it has to be stubbed to make testing possible.
function RandomNumber() {
  var min = 1;
  var max = 10;

  this.generate = function() {
    return min + Math.floor(Math.random() * max);
  }
};

// We pass RandomNumber explicitly, as a parameter in FortuneCookie's constructor.
function FortuneCookie(randomNumber) {
  this.prophecy = function() {
    // FortuneCookie is not aware of how to instantiate RandomNumber - it just uses the provided instance.
    return "Your lucky number is: " + randomNumber.generate();
  }
};

describe("FortuneCookie", function() {
  it("gives a prophecy with a random lucky number", function() {
    // We create a fake substitute of the RandomNumber...
    var randomNumberStub = {
      generate: function() { return 5; }
    };

    // ... and pass the fake substitute to FortuneCookie's constructor, so the FortuneCookie can be tested in a deterministic way.
    var fortuneCookie = new FortuneCookie(randomNumberStub);

    expect(fortuneCookie.prophecy()).toEqual("Your lucky number is: 5");
  });
});

Constructor stubbing example

// RandomNumber's behavior is non-deterministic - it has to be stubbed to make testing possible.
function RandomNumber() {
  var min = 1;
  var max = 10;

  this.generate = function() {
    return min + Math.floor(Math.random() * max);
  }
};

// FortuneCookie's constructor takes no parameters.
function FortuneCookie() {
  this.prophecy = function() {
    // FortuneCookie is responsible for instantiating RandomNumber.
    var randomNumber = new RandomNumber();
    return "Your lucky number is: " + randomNumber.generate();
  }
};

describe("FortuneCookie", function() {
  var originalRandomNumberConstructor = RandomNumber;

  it("gives a prophecy with a random lucky number", function() {
    // Instead of creating a fake substitute of the RandomNumber, we change the implementation of the real RandomNumber for a moment ...
    RandomNumber = function() {
      this.generate = function() { return 5; };
    };

    // ... in effect, FortuneCookie instantiates and uses fabricated implementation of the RandomNumber, without even "knowing" about it - and can be tested in a deterministic way.
    var fortuneCookie = new FortuneCookie();

    expect(fortuneCookie.prophecy()).toEqual("Your lucky number is: 5");
  });

  afterEach(function() {
    RandomNumber = originalRandomNumberConstructor;
  });
});

Pros and cons

complexity

Both examples look almost identical regarding complexity, but there is one thing missing from the picture: in the case of Dependency Injection you have to somehow inject the dependency in your production code. This requires either using a Dependency Injection framework or injecting all the dependencies manually, in some top-level “bootstrap” file. Either way, it’s more complicated than in the case of the constructor stubbing – so the constructor stubbing scores a point in this category.

ability to test legacy code

Another advantage of constructor stubbing over Dependency Injection is that it can be used without… well, Dependency Injection. When a legacy code wasn’t written with a Dependency Injection in mind, retrofitting it means major, risky refactoring – but the constructor stubbing is still possible in such a case, even for the “big ball of mud” type of code. Another point for the constructor stubbing.

explicit API

When you pass a dependency explicitly, as a parameter, it becomes a part of the tested unit’s public API. In dynamic languages, this API is not as well defined as in statically typed ones (you see only that there is a parameter required, but you don’t know the type of that parameter, what methods it should have etc.), but it’s still some information. When you rely on the constructor stubbing, there is no information at all – even the fact that there is a dependency is hidden. This makes it harder to understand the tests, because it’s not clear, without digging deep into the tested unit’s implementation, what the test is stubbing and why. So, the point for readability goes to Dependency Injection.

brittleness

This is related to the explicit API. In the case of constructor stubbing, the tests are coupled to the implementation details of the tested unit. Implementation is much more likely to change than the constructor signature. When you modify implementation, it’s also hard to notice the impact the change will have on the tests – when you change the constructor signature this relation is obvious. In effect, stubbing constructors results in more brittle tests – and another point goes to the Dependency Injection.

Conclusion

The results may at the first glance seem even (2:2). However, the benefits of the two approaches aren’t of the same weight. Brittle and unreadable tests are a much bigger problem than more verbose app bootstrapping. The Dependency Injection is a clear winner – and provides a lot of benefits even in dynamic languages.

The one legitimate benefit of constructor stubbing is that it makes it possible to test legacy code. However, you should treat such tests as temporary characterization tests that will allow you to safely refactor your code into a cleaner, Dependency Injection-based design – keeping them for too long will make your test suite hard to maintain.

What do you think?

What’s your experience? Do you see any other benefits or drawbacks of constructor stubbing vs Dependency Injection? Which method do you prefer? I’d love to hear from you, so please drop me a comment!

Advertisements

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s