3

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.

3
  • You can't have module after calling inject, that's what the error says. beforeEach is preferred way to do this. Otherwise a less readable way to do this is it('...', () => { module(...); inject((...) => {...}) }). Commented Jun 6, 2017 at 11:15
  • @estus What would such a solution look like? The problem is I need to grab a $q before calling module, and in order to do that I need inject (as far as I know, I'm still ramping up on angular/jasmine) Commented Jun 6, 2017 at 11:19
  • Something like that. It's not clear what doc is for, so it depends on the case if it becomes a showstopper or not. Any way, this pattern will be too inflexible most times, so it's likely an antipattern. Commented Jun 6, 2017 at 12:27

1 Answer 1

2

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);
    }
  };
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, that worked. Factories were the piece of the puzzle I was missing. I'll post my final solution later, for future reference.
As a site note: not only is setting up your mocks inside the test itself most definitely not an anti pattern, it is also required when different tests have different setups. Also, generalized setups often cause unnecessary code to run before each test; people end up stuffing everything they need inside a single beforeEach. There's a good reason why test frameworks like xunit decided NOT to provide a beforeEach/SetUp and TearDown mechanisms. See this.
@dcastro We're talking about Angular testing, and it's an antipattern there. Angular is opinionated, so is angular-mocks. It works best with stuff like beforeEach(module(...)). The problem is not self-contained test (I don't think that xunit approach has a real reason, it's a matter of taste) per se but the way it is DRYed up with createViewer helper. If you will try to reimplement angular-mocks functionality by yourself for code above, you will run into the same chicken-egg situation. A pattern just doesn't work for Angular DI. I updated the answer with full sequence for self-contained test

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.