Reputation: 786
I have an angular service responsible for loading a config.json file. I would like to call it in my run phase so I set that json in my $rootContext and hence, it is available in the future for everyone.
Basically, this is what I've got:
angular.module('app.core', []).run(function(CoreRun) {
CoreRun.run();
});
Where my CoreRun service is:
angular.module('app.core').factory('CoreRun', CoreRun);
CoreRun.$inject = ['$rootScope', 'config'];
function CoreRun($rootScope, config) {
function run() {
config.load().then(function(response) {
$rootScope.config = response.data;
});
}
return {
run: run
};
}
This works fine and the problem comes up when I try to test it. I want to spy on my config service so it returns a fake promise. However, I cannot make it since during the config phase for my test, services are not available and I cannot inject $q.
As far as I can see the only chance I have to mock my config service is there, in the config phase, since it is called by run block.
The only way I have found so far is generating the promise using jQuery which I really don't like.
beforeEach(module('app.core'));
var configSample;
beforeEach(module(function ($provide) {
config = jasmine.createSpyObj('config', [ 'load' ]);
config.load.and.callFake(function() {
configSample = { baseUrl: 'someurl' };
return jQuery.Deferred().resolve({data: configSample}).promise();
});
provide.value('config', config);
}));
it('Should load configuration using the correspond service', function() {
// assert
expect(config.load).toHaveBeenCalled();
expect($rootScope.config).toBe(configSample);
});
Is there a way to make a more correct workaround?
EDIT: Probably worth remarking that this is an issue just when unit testing my run block.
Upvotes: 6
Views: 1485
Reputation: 13319
Seems that it is not possible to inject $q
the right way, because function in your run()
block fires immediately. run()
block is considered a config phase in Angular, so inject()
in tests only runs after config blocks, therefore even if you inject()
$q
in test, it will be undefined
, because run()
executes first.
After some time I was able to get $q
in the module(function ($provide) {})
block with one very dirty workaround. The idea is to create an extra angular module and include it in test before your application module. This extra module should also have a run()
block, which is gonna publish $q
to a global namespace. Injector will first call extra module's run()
and then app module's run()
.
angular.module('global $q', []).run(function ($q) {
window.$q = $q;
});
describe('test', function () {
beforeEach(function () {
module('global $q');
module('app.core');
module(function ($provide) {
console.log(window.$q); // exists
});
inject();
});
});
This extra module can be included as a separate file for the test suite before spec files. If you put the module in the same file where the tests are, then you don't event need to use a global window
variable, but just a variable within a file.
Here is a working plunker (see a "script.js" file)
First solution (does not solve the issue):
You actually can use $q
in this case, but you have to inject it to a test file. Here, you won't really inject it to a unit under test, but directly to a test file to be able to use it inside the test. So it does not actually depend on the type of a unit under test:
// variable that holds injected $q service
var $q;
beforeEach(module(function ($provide) {
config = jasmine.createSpyObj('config', [ 'load' ]);
config.load.and.callFake(function() {
var configSample = { baseUrl: 'someurl' };
// create new deferred obj
var deferred = $q.defer();
// resolve promise
deferred.resolve({ data: configSample });
// return promise
return deferred.promise;
});
provide.value('config', config);
}));
// inject $q service and save it to global (for spec) variable
// to be able to access it from mocks
beforeEach(inject(function (_$q_) {
$q = _$q_;
}));
Resources:
inject()
- angular.mock.injectAnd one more note: config phase and run phase are two different things. Config block allows to use providers only, but in the run block you can inject pretty much everything (except providers). More info here - Module Loading & Dependencies
Upvotes: 4