How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?
Solution 1
You need to inject $rootScope
in your test and trigger $digest
on it.
Solution 2
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
Solution 3
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest
. So, here is what I came up with.
First off why? You need to use $rootScope.$digest
whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q
which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope
into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest
as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling
done
at the wrong time. You need to calldone
inside of thethen
method (or thecatch
orfinally
), of the promise, after is resolves. You are calling it before the promise resolves, which is causing theit
clause to terminate.
Solution 4
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
Related videos on Youtube
![Terry](https://i.stack.imgur.com/u7wPk.jpg?s=256&g=1)
Terry
I build web applications for the modern enterprise. I value efficiency, stability, performance & elegance. GitHub Twitter
Updated on September 26, 2020Comments
-
Terry almost 4 years
It seems that promises do not resolve in Angular/Jasmine tests unless you force a
$scope.$digest()
. This is silly IMO but fine, I have that working where applicable (controllers).The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) { return { getSomething: function() { var deferred = $q.defer(); deferred.resolve('test'); return deferred.promise; } } });
describe('Method: getSomething', function() { // In this case the expect()s are never executed it('should get something', function(done) { var promise = myService.getSomething(); promise.then(function(resp) { expect(resp).toBe('test'); expect(1).toEqual(2); }); done(); }); // This throws an error because done() is never called. // Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. it('should get something', function(done) { var promise = myService.getSomething(); promise.then(function(resp) { expect(resp).toBe('test'); expect(1).toEqual(2); done(); }); }); });
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) { var promise = myService.getSomething(); promise.then(function(resp) { expect(resp).toBe('test'); }); $rootScope.$digest(); done(); });
-
Doug about 9 yearsI have an ng service with two functions which return different $q promises. In jasmine, I could not get the test for either function to work with any of your suggestions. I got jasmine timeout error. My .then(cb) handler in my test was calling the jasmine done handler as evidenced by console msgs around done call. Only thing that worked was $digest() in the service itself after the resolve. But to make things weirder, the second service function got an ng error saying that the "digest was already running". So I commented this digest out and all is good but no idea why. Now, that is silly.
-
Igonato about 9 years
done
call at the end of the test doesn't make sense. The test isn't really asynchronous at this point. It should be called at the end of.then
anonymous function. And in case the promise is truly asynchronous itself instead of calling digest once at the end it should be something likesetInterval($rootScope.$digest, 100)
-
-
Terry about 10 yearsIt works but seems a bit ridiculous, I was hoping there was an alternative. I suppose that's an issue for the Angular team.
-
pkozlowski.opensource about 10 yearsWell, the problem is that current implementation of promises is tied to the digest cycle (actually it is non-trivial problem to solve as we don't have an equivalent of the
nextTick
method in a browser). So yes, I'm afraid that it is your only option as long as propagation of promise results is tied to the $digest cycle in AngularJS. -
Blake over 9 yearsI tried this, but am now getting a 'No more request expected' error.
-
paulhhowells almost 9 years
$timeout
will run$apply
, so I don’t believe you need to use$rootScope.$apply
inside the$timeout
. Try just:$timeout(function(){ deferred.resolve(); });
N.B.: I am hoping this will help @Jeffrey A. Gochin, not recommending this as a solution for the OP -
Jeffrey A. Gochin almost 9 yearsYou know what, after playing with this for a while, I realized that the real problem is Angular is this case. The way they implement the mocks for $timeout are what causes this issue. My own testing, I have decided to just create my own test harness in the browser. Without angular mocks. When I do this the problem goes away. The problem now is how to still automate the testing.
-
Anthony Joanes over 8 yearsI'm doing similar in a unit test but when I call $rootScope.$apply(); it actually triggers something in a module which I though was mocked and trys to do an http GET?
-
danday74 over 8 yearssuccinct - maybe too succinct but useful nonetheless
-
Jeffrey A. Gochin almost 8 yearsAs a followup to this. I have started using Protractor and Selenium to automate my test harness.
-
Janey about 6 yearsHi I know this is old but I've just updated to AngularJS 1.7.2 and this is no longer working. I get the error: Error: Unexpected request: GET /api/auth/params error properties: Object({ $$passToExceptionHandler: true })
-
Ajay.k over 5 years@pkozlowski.opensource I have an issue to get data from controller can you please look into it. Can you help me on it. stackoverflow.com/questions/53629138/…