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.
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: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 anangular.module(...).service
block, Angular is doing something like the above code behind your back.The other pitfall is that
angular.mock.inject
, which is theinject
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:
The
'testAssist'
module in the fixture (beforeEach
) exists for the sole purpose of having a configuration time dependency onuiGmapGoogleMapApiProvider
, so I can capture the latter in the localgmapProvider
variable. The subsequent calls tomodule
andinject
are bookkeeping tricks to ensure that theconfig
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 aconfigure
method. Note that the firstangular.module
call is a regular module definition while the secondmodule
call is a special construct from the mocking framework (angular.mock
).I have pushed the above solution to the fix1 branch on GitHub.
You cannot obtain a provider instance by using
inject
, usemodule
instead