Unit test when loading things at app run with AngularJS

All we need is an easy explanation of the problem, so here it is.

I need my app to run some configuration at runtime vi an HTTP endpoint.

I wrote a simple service to do that:

module.factory('config', function ($http, analytics) {
    return {
        load: function () {
            $http.get('/config').then(function (response) {
                analytics.setAccount(response.googleAnalyticsAccount);
            });
        }
    }
});

Next, I call this module in a run block of my app module:

angular.module('app').***.run(function(config) {
        config.load();
    });

All is working well when the app is running but in my unit tests, I get this error: “Error: Unexpected request: GET /config”

I know what it means but I don’t know how to mock it when it happens from a run block.

Thanks for your help

EDIT to add spec

Calling this before each

beforeEach(angular.mock.module('app'));

Tried this to mock $httpBackend:

beforeEach(inject(function($httpBackend) {

    $httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount':});

    angular.mock.module('app')

    $httpBackend.flush();
}));

But got:

TypeError: Cannot read property 'stack' of null
        at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
    TypeError: Cannot read property 'stack' of null
        at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
    TypeError: Cannot read property 'stack' of null
        at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)

EDIT since update to AngularJS 1.0.6

Since I’ve updated to AngularJS 1.0.6, advised by Igor from the Angular team, the issue is gone but now I’ve now got this one, which sounds more “normal” but I still can’t figure out how to make it works.

Error: Injector already created, can not register a module!

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I struggled with this error for a little while, but managed to come up with an sensible solution.

What I wanted to achieve is to successfully stub the Service and force a response, on controllers it was possible to use $httpBackend with a request stub or exception before initiating the controller.
In app.run() when you load the module it initialises the object and it’s connected Services etc.

I managed to stub the Service using the following example.

describe('Testing App Run', function () {
    beforeEach(module('plunker', function ($provide) {
        return $provide.decorator('config', function () {
            return {
                load: function () {
                    return {};
                }
            };
        });
    }));


    var $rootScope;
    beforeEach(inject(function (_$rootScope_) {
        return $rootScope = _$rootScope_;
    }));

    it("defines a value I previously could not test", function () {
        return expect($rootScope.value).toEqual('testing');
    });

});

I hope this helps your app.run() testing in the future.

Method 2

I don’t know if you are still looking for an answer to this question. But here is some information that might help.

$injector is a singleton for an application and not for a module. However, angular.injector will actually try to create a new injector for each module (I suppose you have a

beforeEach(module("app")); 

at the beginning.

I had the same problem while using Angular, RequireJS, Karma and Jasmine and I figured out two ways to solve it. I created a provider for the injector function as a separate dependency in my tests. For example MyInjectorProvider which provides a singleton instance of $injector.

The other way was to move the following statements:

beforeEach(module("app"));
beforeEach(inject(function($injector){
    ...
})

inside the test suite description. So here is how it looked before:

define(['services/SignupFormValidator'], function(validator){
    var validator;
    beforeEach(module("app"));
    beforeEach(inject(function($injector){
        validator = $injector.get("SignupFormValidator");
    })

    describe("Signup Validation Tests", function(){
        it("...", function(){...});
    });
});

After applying the fix it looks like this:

define(['services/SignupFormValidator'], function(validator){
    var validator;

    describe("Signup Validation Tests", function(){
        beforeEach(module("app"));
        beforeEach(inject(function($injector){
            validator = $injector.get("SignupFormValidator");
        });

        it("...", function(){...});
    });
});

Both the solutions worked in my case.

Method 3

You should mock every HTTP request with ngMock.$httpBackend. Also, here is a guide.


Update

You don’t need the angular.mock.module thing, just need to inject your app module. Something like this:

var httpBackend;

beforeEach(module('app'));
beforeEach(inject(function($httpBackend) {
  httpBackend = $httpBackend;
  $httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount': 'something'});
}));

In your tests, when you need the mocked http to answer, you will call httpBackend.flush(). This is why we have a reference to it, so you don’t need to inject it in every single test you have.

Note you will need to load angular-mock.js in order to it work.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply