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:
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.
You cannot obtain a provider instance by using inject
, use module
instead