Unit Testing Angular When The $inject Property, Jasmine Spy Objects, and Promises Are Involved
I recently encountered an issue when writing a unit test for an Angular controller which was using the $inject property to specify its dependencies: even though I was using Jasmine to create mocks of the dependencies, the $inject property was overriding those and using the $provide service to instantiate new instances of the objects based on name.
I went about setting up a beforeEach() call in the test spec to associate my mocks with the $provide service. However, I couldn't set up one of the mocks beforehand because it returns promises and I therefore needed to make use of the $q service to set up the mock. I tried to add an empty {} object to the $provide service, but this didn't work (I received different errors in the various stages of my work on this, from errors about a function being undefined, to one about there not being a constructor). What I had was a "chicken or the egg" situation: the $provide service registers providers for the $inject service to use, but I needed that $q service to set up the mock that I would be returning via the $provide service. Ugh.
The solution was a bit of a hack, and I'm honestly surprised I didn't find more about this issue on the web (I did find some posts about it, from people having the same issue when promises were involved and Jasmine was being used to create spy objects -- it's this specific an issue).
Here's how I solved the issue.
First, I created a spec-level variable to contain a reference to the $provide service, and I set up a beforeEach() call to assign it:
var _provide;
beforeEach(module("nl.tpl-directives"));
beforeEach(module("Newsletter"));
beforeEach(module(function ($provide) {
_provide = $provide;
}));
I went about setting up a beforeEach() call in the test spec to associate my mocks with the $provide service. However, I couldn't set up one of the mocks beforehand because it returns promises and I therefore needed to make use of the $q service to set up the mock. I tried to add an empty {} object to the $provide service, but this didn't work (I received different errors in the various stages of my work on this, from errors about a function being undefined, to one about there not being a constructor). What I had was a "chicken or the egg" situation: the $provide service registers providers for the $inject service to use, but I needed that $q service to set up the mock that I would be returning via the $provide service. Ugh.
The solution was a bit of a hack, and I'm honestly surprised I didn't find more about this issue on the web (I did find some posts about it, from people having the same issue when promises were involved and Jasmine was being used to create spy objects -- it's this specific an issue).
Here's how I solved the issue.
First, I created a spec-level variable to contain a reference to the $provide service, and I set up a beforeEach() call to assign it:
var _provide;
beforeEach(module("nl.tpl-directives"));
beforeEach(module("Newsletter"));
beforeEach(module(function ($provide) {
_provide = $provide;
}));
Next, I set up another beforeEach() using inject(), and I supply the $q service and set up the controller without passing in any dependencies (as they'll be ignored in favor of what the controller uses via $inject anyway).
beforeEach(inject(function ($injector, $rootScope, $controller, $q) {
_scope = $rootScope.$new();
setupServiceMocks($q);
//Controller gets its dependencies via the $inject property, so we need to register them with the $provide
//service
_provide.value('$scope', _scope);
_provide.value('UserService', _userSrviceMock);
_provide.value('$mdDialog', _mdDialogMock);
_controller = $controller("HomeController");
}));
Not an elegant solution, but so far the best I've found.
Comments
Post a Comment