Unit Testing AngularJS Controllers, Views, and More: Part Six, Injecting Indirect Dependencies

This is a follow-up to the series of posts on using Duck-Angular for unit testing controller, views, and bindings (Part 1, Part 2, Part 3, Part 4, and Part 5), Duck-Angular and AngularJS-RequireJS-Seed (which is what the code in this series of posts is based on) now support AngularJS 1.2.9.

What do I mean by Indirect Dependencies? It’s those dependencies that are associated with your controller/service through at least one level of indirection. Simply put, if X depends upon Y, and Y depends upon Z, X depends upon Z. Because all objects are built from the base up, they are transitive, regardless of the complexity of the hierarchy. So far, we’ve seen how to inject dependencies directly into the object(s) under test. So, what about indirect dependencies? Well, it was always possible using Duck-Angular, just slightly unwieldy to set up. No more.

Let’s start with a simple service with no dependencies:

define([], function(){
    return function() {
        this.getSomeData = function() {
            return "True Data from Svc 3";
        };
    };
});

We’ll create another service which depends upon service3. Let’s imaginatively call this service2.

define([], function(){
    return function($q, svc3) {
        var dataFromService3 = svc3.getSomeData();
        this.get = function() {
            var d = $q.defer();
            d.resolve({fromService2: "Some Data", fromService3: dataFromService3});
            return d.promise;
        };
    };
});

We intend to write a test for controller2, as a Duck-Angular test. Note that we are not mocking out service2; we want to preserve it’s original behaviour, but mock out it’s dependency service3.

it("can mock out indirect dependencies", function () {
  var service3Mock = { getSomeData: function() {
    return "Mock Service 3 Data";
  }};
  return mother.createMvc("route2Controller", "../templates/route2.html", {}, null, {service3: service3Mock}).then(function (mvc) {
    var dom = new DuckDOM(mvc.view, mvc.scope);
    expect(dom.element("#data")[0].innerText).to.eql("Some Data, Mock Service 3 Data");
  });
});

The API from spec-helper.js has the mvc() function, which looks like this:

 mother.createMvc = function createMvc(controllerName, templateUrl, dependencies, options, appDependencies) {
 ...
 };

The dependencies parameter serves for injecting direct dependencies. The appDependencies parameter is of interest here. Anything that you pass in here as a map, will be registered with AngularJS during the bootstrap process under the name corresponding to the key. This is exactly what happens when we inject service3Mock in our test.