Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this test for the angular-google-maps provider fail?

I'm trying to test a module that uses angular-google-maps. It is failing because angular.mock.inject cannot find uiGmapGoogleMapApiProvider:

Error: [$injector:unpr] Unknown provider: uiGmapGoogleMapApiProviderProvider <- uiGmapGoogleMapApiProvider

I can't figure out what is going wrong. Here is the reduced testcase:

'use strict';

describe('this spec', function() {
    beforeEach(module('uiGmapgoogle-maps'));

    it('tries to configure uiGmapGoogleMapApiProvider', inject(function(uiGmapGoogleMapApiProvider) {
        expect(uiGmapGoogleMapApiProvider.configure).toBeDefined();
    }));
});

The entire thing is available as a ready-to-run Angular project from GitHub. If you find the problem, please answer here on Stack Overflow. Bonus points if you also submit a pull request to the GitHub repository.

like image 681
Julian Avatar asked Aug 11 '16 16:08

Julian


People also ask

Does Google Maps use angular?

The new Angular Component pearl-lullaby (v9. 0.0-rc. 0) introduces the second official @angular/component component, a Google Maps component.


1 Answers

Two pitfalls are at interplay here, neither of which has anything to do with angular-google-maps.

The first pitfall lies in the distinction between services and providers. The documentation states that services, factories, values and constants are special cases of providers. To a relative beginner like me, this seems to suggest that providers and services can be dependency-injected anywhere in the same way. However, the opposite is true: in any place where dependencies can be injected, you can inject either providers or services but never both.

The reason for this divide lies in the strict separation between configuration time and run time (see module documentation). Providers are available during configuration time while services are available during run time. Run time starts after configuration time ends. .config and .provider blocks are executed at configuration time while most other types of blocks get executed at run time. The relation between provider and service definitions is illustrated in the following snippet of code, adapted from the provider documentation:

myModule.provider('myServiceProvider', ['injectedProvider', function MyServiceProvider(injectedProvider) {
    // configuration time code depending on injectedProvider

    this.$get = ["injectedService", function MyService(injectedService) {
        // run time code depending on injectedService
    }];

    // more configuration time code
}]);

As you can see, a service is defined within a provider. The provider is defined at configuration time (outer block, function MyServiceProvider) and may depend on other providers. The service can be extracted from the provider using the provider's .$get method, at run time, as defined by the inner block (function MyService), and may depend on other services. A provider cannot be an injected dependency of a service or vice versa, but you can nest a service definition inside a provider definition like above to make it depend on providers indirectly. When you define a "standalone" service using an angular.module(...).service block, Angular is doing something like the above code behind your back.

The other pitfall is that angular.mock.inject, which is the inject from the unit test in my question, can only do run time injections. For configuration time injections you have to do "the real thing", i.e. non-mocked injection, by creating a new module with configuration time dependencies. This is what mguimard hinted at. André Eife published a short tutorial on how to do this, which I found through a link at the bottom of an answer to my other question.

In conclusion, here is the code that would fix the problem in my question:

'use strict';

describe('this spec', function() {
    var gmapProvider;

    beforeEach(function() {
        angular.module('testAssist', ['uiGmapgoogle-maps'])
        .config(function(uiGmapGoogleMapApiProvider) {
            gmapProvider = uiGmapGoogleMapApiProvider;
        });
        module('testAssist');  // angular.mock.module
        inject();              // angular.mock.inject
    });

    it('tries to configure uiGmapGoogleMapApiProvider', function() {
        expect(gmapProvider.configure).toBeDefined();
    });
});

The 'testAssist' module in the fixture (beforeEach) exists for the sole purpose of having a configuration time dependency on uiGmapGoogleMapApiProvider, so I can capture the latter in the local gmapProvider variable. The subsequent calls to module and inject are bookkeeping tricks to ensure that the config block of 'testAssist' gets executed. Thanks to the capture, no injection needs to be done within the testcase (it) and I can just verify that the provider has a configure method. Note that the first angular.module call is a regular module definition while the second module call is a special construct from the mocking framework (angular.mock).

I have pushed the above solution to the fix1 branch on GitHub.

like image 194
Julian Avatar answered Oct 21 '22 13:10

Julian