dcastro
dcastro

Reputation: 68670

AngularJS tests - inject -> module -> inject

I'm trying to test a service documentViewer that depends on some other service authService

  angular
    .module('someModule')
    .service('documentViewer', DocumentViewer);

  /* @ngInject */
  function DocumentViewer($q, authService) {
    // ...

    this.view = function(doc) {
      //...
    }
  }

This is what my test looks like at the moment

it('test', inject(function($q) {
  var doc = {
    view: function() {
      return $q.resolve(0);
    }
  };

  var auth = {
    refreshProfileData: function() {
      return $q.resolve(0);
    },
  };

  var viewer = createViewer(auth);
}));

function createViewer(auth) {
  var viewer;

  module({
    authService: auth
  });
  inject(function(documentViewer) {
    viewer = documentViewer;
  });

  return viewer;
}

The problem is I need to call inject to grab a $q, then use it to create my mocks, register my mocks with module, and then call inject again to grab the unit under test.

This results in

Error: Injector already created, can not register a module! in bower_components/angular-mocks/angular-mocks.js (line 2278)

I've seen lots of answers here on SO saying you can't call module after inject, but they don't offer any alternative to a scenario like the above.

What's the correct approach here?

PS: I'd like to avoid using beforeEach, I want each test to be self-contained.

Upvotes: 3

Views: 232

Answers (1)

Estus Flask
Estus Flask

Reputation: 222513

module is used to define which modules will be loaded with inject and cannot be called after inject, this is chicken-egg situation.

The object accepted by module is used to define mocked services with $provide.value:

If an object literal is passed each key-value pair will be registered on the module via $provide.value, the key being the string name (or token) to associate with the value on the injector.

There can be no more than 1 function like createViewer that calls both module and inject. If this means that this kind of self-contained test is an antipattern, there is nothing that can be done about that. Angular testing works best with usual habits, including beforeEach and local variables.

In order to eliminate the dependency on $q, mocked service can be made a factory.

it('test', function () {
  var authFactory = function ($q) {
    return {
      refreshProfileData: function() {
        return $q.resolve(0);
      },
    };
  };

  // mocks defined first
  module(function ($provide) {
    $provide.factory('authService': authFactory);
  });

  var viewer;
  inject(function(documentViewer) {
    viewer = documentViewer;
  });
  // no module(...) is allowed after this point

  var $q;
  inject(function(_$q_) {
    $q = _$q_;
  });

  var doc = {
    view: function() {
      return $q.resolve(0);
    }
  };
});

Upvotes: 2

Related Questions