Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS Unit Testing - Various patterns for injecting dependencies

I'm new to unit testing and am mainly learning from examples that I find. The problem is that I've seen so many different patterns that it's hard to understand what the differences are between them. And how to combine those patterns for various use cases. Below is one such pattern:

    var $rootScope, $window, $location;
    beforeEach(angular.mock.module('security.service', 'security/loginModal.tpl.html'));

    beforeEach(inject(function(_$rootScope_, _$location_) {
        $rootScope = _$rootScope_;
        $location = _$location_;
    }));

    var service, queue;
    beforeEach(inject(function($injector) {
        service = $injector.get('security');
        queue = $injector.get('securityRetryQueue');
    }));

So from this pattern, I've gleaned that Angular core services/providers should be injected with the underscore pattern where as other 3rd party dependencies or my own dependencies should be done using the $injector.get() pattern. Is this valid? I've noticed I can do $injector.get() with Angular core services and it will still work so maybe it's just convention to do it this way? Also, what is the point of 'security/loginModal.tpl.html' in beforeEach(angular.mock.module('security.service', 'security/loginModal.tpl.html'));? I know that it is an HTML template added to the template cache but what is angular.mock.module doing with it?

I've also seen this less common pattern that throws a monkey wrench in the above logic:

    beforeEach(inject(function($injector, _$location_) {
        security = $injector.get('security');
        $location = _$location_;
    }));

If I can just add services to the inject callback like this code does with $location, that seems like a simpler way of referencing dependencies. Why should I not do this?

Here's another pattern:

    beforeEach(function() {
        module('security.service', function($provide) {
            $provide.value('$window', $window = jasmine.createSpyObj('$window', ['addEventListener', 'postMessage', 'open']));
        });

        inject(function(security) {
            service = security;
        });
    });

From my understanding, the point of this pattern is to initialize "security.service" module with a mocked $window. This makes sense, but how do I fit this pattern in with the previous patterns? i.e. how do I mock 'security/loginModal.tpl.html', how do I inject my Angular core dependencies + my other dependencies?

Lastly, what can I and can't inject in nested describe and it blocks? Is it safe to assume I can't retro-inject mocked services to the module I'm testing. So then what can I inject and what are the use cases?

If there is a definitive documentation source for AngularJS unit testing initialization that would help answer these questions, please point me to it.

like image 498
ravishi Avatar asked Sep 15 '15 09:09

ravishi


1 Answers

I've gleaned that Angular core services/providers should be injected with the underscore pattern where as other 3rd party dependencies or my own dependencies should be done using the $injector.get() pattern

You can use either. The underscore pattern is simply a convenience method to avoid conflicts with local variables of the same name. Consider the following

var $rootScope, myService, http; // these are local variables

beforeEach(inject(function(_$rootScope_, _myService_, $http) {
    $rootScope = _$rootScope_; // underscores to avoid variable name conflict
    myService = _myService_; // same here with your custom service
    http = $http; // local variable is named differently to service
}));

If I can just add services to the inject callback like this code does with $location, that seems like a simpler way of referencing dependencies. Why should I not do this?

You should :)


Also, what is the point of 'security/loginModal.tpl.html' in beforeEach(angular.mock.module('security.service', 'security/loginModal.tpl.html'));?

As far as I know, unless you have an actual module with that name, eg

angular.module('security/loginModal.tpl.html', [])

this will fail. angular.mock.module should only be passed module names, instances or anonymous initialisation functions.


how do I mock 'security/loginModal.tpl.html'

Ideally, you shouldn't. Unit tests should test the API of your code... points of interaction, typically defined by publically accessible methods and properties on your objects.

If you're just trying to prevent Karma from attempting to load templates over HTTP (usually from directive tests), you can use a template pre-processor like karma-ng-html2js-preprocessor


Lastly, what can I and can't inject in nested describe and it blocks? Is it safe to assume I can't retro-inject mocked services to the module I'm testing. So then what can I inject and what are the use cases?

You can run angular.mock.inject just about anywhere (typically beforeEach and it). Mocked services should only be configured in a module or anonymous module initialisation function (as in your example with $provide and $window) and typically after your own module(s) (ie "security.service") in order to override the real services by replacing them in the injector. Once you've run inject(), you cannot retro-actively replace a service with a mock.

like image 152
Phil Avatar answered Nov 15 '22 06:11

Phil