Finally clause of promise never getting executed in Jasmine test - angularjs

I have this jasmine test, and the finally clause on a promise appears to not be getting executed, as I get the error:
PhantomJS 2.1.1 (Mac OS X 0.0.0) Service: petsFactory .getPetsAsync() should return a list of pets FAILED
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
My test file looks like:
'use strict';
describe('Service: petsFactory', function () {
// load the service's module
beforeEach(module('smokeTestApp'));
// instantiate service
var petsFactory;
beforeEach(inject(function (_petsFactory_) {
petsFactory = _petsFactory_;
}));
describe('.getPetsAsync()', function () {
it('should return a list of pets', function (done) {
var testPets = function (pets) {
expect(Array.isArray(pets)).toBe(true);
}
var failTest = function(error) {
expect(error).toBeUndefined();
};
petsFactory
.getPetsAsync()
.then(testPets)
.catch(failTest)
.finally(done);
});
});
});
The relevant factory method looks like:
var getPetsAsync = function () {
return $q.when(pets);
};
The contents of the pets variable is totally synchronous, the promise is just a wrapper on a synchronous value that is there immediately.
What is going wrong here?

From the documentation:
When testing promises, it's important to know that the resolution of
promises is tied to the digest cycle. That means a promise's then,
catch and finally callback functions are only called after a digest
has run. In tests, you can trigger a digest by calling a scope's
$apply function. If you don't have a scope in your test, you can
inject the $rootScope and call $apply on it. There is also an example
of testing promises in the $q service documentation.
So simply inject $rootScope and use $apply:
petsFactory
.getPetsAsync()
.then(testPets)
.catch(failTest)
.finally(done);
$rootScope.$apply();

Related

Jasmine unit test for angular service In controller

I'm new to jasmine framework. I've gone through some tutorials and learned and started writing unit tests. 'facing one issue here is the description.
I have a controller where i can invoke a service call to get the data. See the code below.
$scope.getEmpInfo = function() {
EmpService.getInfo($scope.empid)
.then(function(data) {
$scope.empData = data;
$scope.populateEmpData($scope.empData);
}, function(reason) {
//do nothing
}
}
Now, i want to write a unit test for the above method. Im able to make a spy on serice using promise but i wasnt able to spy $scope.populateEmpData(). here is my test case.
describe('Emp data', function() {
var d, scope;
beforeEach(function() {
module("emp");
module("emo.info");
});
describe('empcontroller', function() {
beforeEach(inject(function($q,_EmpService_, $controller,$rootScope){
d = $q.defer();
empService = _EmpService_;
spyOn(empService,"getInfo").and.returnValue(d.promise);
scope = $rootScope.$new();
empCtrl = $controller("empController", {
$scope: scope,
});
}));
it('should get the Employee information ', function() {
scope.getEmpInfo();
spyOn(scope,'populateEmpData');
expect(EmpService.getInfo).toHaveBeenCalled();
//Here im getting the error.
expect(scope.populateEmpData).toHaveBeenCalled();
});
});
});
Please help resolve this issue. Thanks in advance.
It's because you are not resolving promise. You will have to make change in spyOn.
- spyOn(empService,"getInfo").and.callFake(function() {
return {
then : function(success, error) {
success();
}
} }
Now, it will go into the success callback and will try to call $scope.populateEmpData();
You're never resolving your promise. And you need to call $scope.$apply().
Why is this necessary? Because any promises made with the $q service
to be resolved/rejected are processed upon each run of angular’s
digest cycle. Conceptually, the call to .resolve changes the state of
the promise and adds it to a queue. Each time angular’s digest cycle
runs, any outstanding promises will be processed and removed from the
queue.
Unit Testing with $q Promises in AngularJS
Check it out above link it will help you.

Angularjs Mocha test $q promises without $rootScope.$apply

I have this service:
angular.module('domeeApp')
.factory('streamWidget', streamWidgetFactory);
function streamWidgetFactory($q) {
return {
loadContent: function() {
return $q(function(resolve, reject) {
resolve('test');
})
}
}
}
I'm testing it with karma/mocha/chai:
describe('streamWidget', function() {
beforeEach(module('domeeApp'));
var streamWidget;
var $timeout;
beforeEach(inject(function(_$timeout_, _streamWidget_) {
streamWidget = _streamWidget_;
$timeout = _$timeout_;
}));
it('should load new content', function(done) {
streamWidget.loadContent()
.then(function(res) {
expect(res).to.equal('test');
done();
})
.catch(function(){})
$timeout.flush();
});
});
Since $q promises doesn't work well with mocha i'm following this answer, which says to add $timeout.flush() to force the .then method of the promise to be executed.
The problem is, after calling .flush(), all my app wakes up and i start to get this errors from angular-mocks:
Error: Unexpected request: GET /partials/page/view/index.
I know about $httpBackend, but it would be insane to mock ALL the requests my app is making on startup.
Is there a way to make $q promises work with mocha without calling $timeout.flush() or $rootScope.$apply()?
As shown here, chai-as-promised can be used to assert $q promises.
With this setup
chaiAsPromised.transferPromiseness = function (assertion, promise) {
assertion.then = promise.then.bind(promise);
if (!('$$state' in promise))
return;
inject(function ($rootScope) {
if (!$rootScope.$$phase)
$rootScope.$digest();
});
};
digest cycles will be triggered automatically on promise assertions, executing the whole promise chain.
In this case the spec
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
$rootScope.$digest();
});
can omit $digest() call and become
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
});
Notice that $q promises are synchronous, they shouldn't be returned from Mocha spec or call done callback.
Here's an alternative strategy that we use because we never actually need $httpBackend, but it sometimes (randomly) fails making requests for templates used by directives (even though those templates are available in $templateCache):
beforeEach(function() {
module('app', function($provide) {
// This is using jasmine, but the idea is the same with mocha.
// Basically just replace $httpBackend with a function that does nothing.
$provide.constant('$httpBackend', jasmine.createSpy('$httpBackend'));
});
});
Of course, if you actually use $httpBackend in other cases, then this won't work, as you'll need it to mock response objects.

$httpBackend.flush() method throws Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting

I am trying to unit test my AngularJS application using Karma and Jasmine. I want to mock the $http service. For that, I am using the $httpBackend method. Below is my service that I want to test:
angular.module('MyModule').factory('MyService', function($http, $log, $parse, $q, $timeout, $filter, MyOtherService1, MyOtherService2){
var service = {};
service.getSomething = function(id){
return $http.get('/somePath/subpath/' + id);
}
});
My unit test for this service is:
describe("myTest", function(){
var myService, $httpBackend, scope, mockMyOtherService1, mockMyOtherService2;
var myResponse =
{
foo:'bar'
};
beforeEach(module("MyModule"));
beforeEach(inject(function(_MyService_, $injector){
$httpBackend = $injector.get('$httpBackend');
myService = _MyService_;
scope = $injector.get('$rootScope').$new();
mockMyOtherService1 = $injector.get('MyOtherService1');
mockMyOtherService2 = $injector.get('MyOtherService2');
}));
beforeEach(function(){
//To bypass dependent requests
$httpBackend.whenGET(/\.html$/).respond(200,'');
});
//If I uncomment the below afterEach block, the same error is shown at next line.
/*afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});*/
//This test passes successfully
it("should check if service is instantiated", function () {
expect(myService).toBeDefined();
});
//This test passes successfully
it("should expect dependencies to be instantiated", function(){
expect($httpBackend).toBeDefined();
});
//The problem is in this test
it("should get the getSomething with the provided ID", function() {
$httpBackend.whenGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
//The error is shown in next line.
$httpBackend.flush();
//If I comment the $httpBackend.flush(), in the next line, the $$state in deferredResponse shows that the Object that I responded with is not set i.e. it does not matches the 'myResponse'.
expect(deferredResponse).toEqual(myResponse);
});
});
This is an emergency problem and I need help regarding the same as soon as possible. I will be very grateful for your answer.
The problem was I needed to inject $location in my spec files even though they are not injected in the services. After injection, all worked well! Hope this helps someone who gets stuck in the same situation.
You will get a promise from your service. So change your test code to:
//The problem is in this test
it("should get the getSomething with the provided ID", function (done) {
$httpBackend.expectGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
deferredResponse.then(function (value) {
expect(value.data).toEqual(myResponse);
}).finally(done);
$httpBackend.flush();
});
I've recently had this problem when updating a project from Angular 1.2 to 1.4. The test code looked something like:
it('should call /something', function(){
httpBackend.expectGET('/something').respond(200);
scope.doSomething();
httpBackend.flush();
});
The error was the infdig past 10 iterations. It was caused by invoking the .flush() method. I figured out this is seemingly because there were no pending promises created within doSomething().
Once I added a promise somewhere within doSomething() or inner methods, the infdig problem went away.
I suspect - and this is 100% speculation so don't let it influence your development - this is because httpBackend does some trickery to wait for promises, which maybe involves digesting repeatedly until there's a change. Since there's no promises, there's no changes - infinite digest.

Jasmine mock promise resolve not being handled when called in controller construtor

In my controller constructor, a lot of the private variables are set as the result of a promise returned from a service.
For example, this will be called when the controller is being constructed.
MyService
.initializeDataForType(type)
.then(function (data) {
//never getting hit when testing
});
And the service call is mocked to return a resolved promise like so.
var myService = jasmine.createSpyObj('page.MyService', [
'initializeDataForType'
]);
beforeEach(inject(function ($controller, _$q_) {
myService.initializeDataForType.and.callFake(function (type) {
var deferred = _$q_.defer();
deferred.resolve({});
return deferred.promise;
});
target = $controller('page.MyController', {
'page.MyService': myService
});
}));
The then() method for the service call is never being reached. It seems that jasmine isn't waiting and moving on to the next test.
Have you tried adding $scope.$digest() after instantiating the controller? In jasmine tests, you have to force scope digest cycles and since you variables are set as a result of a promise, a digest cycle needs to be run before testing to see whether those variables exist.

Infinite Digest when Mocking $location

I've discovered that if I try to mock Angular's $location service and then I need to force any sort of digest in my test then I get an infinite digest error. It doesn't matter what the code is in my controller. I have tried this with a completely empty controller which just takes $location as a parameter and if I then do a digest in my test at all it fails.
Here's a fiddle and here's my test from that fiddle:
describe('fooController', function() {
var controller, mocklocation, scope;
beforeEach(function() {
module("myModule");
inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
mocklocation = sinon.stub($location);
controller = $controller("FooController", { $scope: scope, $location: location });
});
});
describe('when calling $digest in a test', function () {
beforeEach(function () {
//do some stuff here and then resolve a promise
//call digest to cause the resolved promise to get evaluated
scope.$root.$digest();
});
it('should not get infinite digests', function () {
});
});
});
Notice that all I'm doing is a scope.$root.$digest in that test. In my real code I was resolving a promise before that digest so that's why I needed to do the digest.
Instead of trying to mock the entire $location service, I suggest you hand-roll your own mock and stub only the thing you need to test. For example, if you are using the mockLocation to assert that the path function is called with a particular path, you could set up your mock like this:
mockLocation = { path: sinon.stub() };
and then you can still assert the same thing like this:
expect(mockLocation.path.calledWith(...)).toBeTruthy();
I'm not sure why stubbing the whole object causes an infinite digest (those are so tricky!) but this should at least allow you to move forward. Also, in your test make sure you send in the mockLocation object when you inject it (right now you have "$location: location").
controller = $controller("FooController", { $scope: scope, $location: mockLocation });

Resources