I'm testing a promise with angularjs jasmine, and sinonjs.
I'm puzzled by something regarding promises. Here is my code:
it('should return data with length 4 ', inject(function ($rootScope) {
var storageData;
mockDualStorage.getData.returns($.when(''));
// mockDualStorage.getData is called by getStorageData
// $rootScope.$digest() // not working here
dataGetter.getStorageData().then(function (data) {
console.log(1);
storageData = data;
});
$rootScope.$digest(); // only working here
console.log(2);
expect(storageData.length).toBe(4)// ok
}));
Couple of things are strange here.
If I put $rootScope.$digest() above the dataGetter.getStorageData() then function is never executed.
When the $rootScope.$digest() is below, then gets executed, and order of console.log is 1,2
Why won't then execute when $rootScope.$digest() is above? As I understand promise is already resolved?
After more carefully reading the documentation, found the answer right there.
Differences between Kris Kowal's Q and $q :
There are two main differences: $q is integrated with the $rootScope.Scope Scope model observation mechanism in angular, which means faster propagation of resolution or rejection into your models and avoiding unnecessary browser repaints, which would result in flickering UI.
AngularJS $q service documentation
Related
This is a very common question, but I have never found the answer that works properly. I have come across three answers, but none always works.
$apply: This will force an update, but will randomly through an error if it gets called while a digest is in progress.
"safe apply" where there is a check for a digest in progress before calling $apply. This doesn't always update the view for some reason I haven't been able to determine. In addition, there is a small chance that a digest will start between the check and $apply.
$timeout: according to the documentation, this should work reliably but doesn't seem to always update the view.
from $timeout documentation, the parameter invokeApply:
If set to false skips model dirty checking, otherwise will invoke fn within the $apply block. (default: true)
It never throughs an error, but sometimes doesn't update the view during a page load.
Here is a code sample where the problem occurs during page initialization:
EditService.getEvents(Gparams.curPersonID)
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
var timer = $timeout(function() { })
.then(function successCallback(response) {
do something
});
$scope.$on("$destroy", function(event {
$timeout.cancel(timer)});
}); });
What is the correct answer? Please don't just say what but also discuss why.
Here is a code sample where the problem occurs during page initialization
A common cause of .then methods not updating the DOM is that the promise is not an AngularJS $q service promise. The solution is to convert the suspect promise to a $q service promise with the $q.when method.
//EditService.getEvents(Gparams.curPersonID)
//CONVERT to $q service promise
$q.when(EditService.getEvents(Gparams.curPersonID))
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
The .then method of a $q service promise is integrated with the AngularJS framework and its digest cycle. Changes to the scope model will automatically update the DOM.
when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
--AngularJS $q Service API Reference - $q.when
I'm trying to automate the testing of Angular services which happen to make calls to Parse.com through Parse SDK.
The problem I have got is that the promises dont get resolved unless I explicitely trigger a digest cycle, and the way my services are done, I have to do that in my services implementations which is not sustainable.
My service code is the following :
factory('myService', function($http, $q, $rootScope) {
var myService = {};
myService.simplePromiseTest = function() {
var p = $q.defer();
var query = new Parse.Query("AnyObjectInParse");
query.find().then(function(results){
p.resolve(results);
// *** I have to include that line for the jasmine test to run ***
$rootScope.$apply();
});
return p.promise;
}
}
return myService;
}
And here is my jasmine test
async.it('should resolve the promise', function(done) {
myService.simplePromiseTest().then(function(results) {
// this is never called if don't trigger the digest from the service code
done();
});
// This line is use less as when I get into that line, the promise is not resolved.
// $scope.$root.$digest();
});
So the situation is as following :
I have to wait for the call to parse to end before triggering a digest cycle
I can't find any other solution than to pollute my service's code with this code
I'd like to find a sustainable solution which doesn't require me to update my service's code to pass the test.
Thanks in advance I'm lost with that, I may be missing something obvious :-)
Call $rootScope.$apply(); in the test itself rather than in the promise implementation. Tests with done are asynchronous so it's ok to resolve it afterwards. Alternatively use Angular 1.3.
In general for testing promises I'd probably recommend mocha rather than Jasmine since it supports promise tests out of the box with return statements.
$scope.$watch('hb.bulkPromise', function (promise) {
promise.then(function (resource) {
return resource.test();
}).then(function (data){
})
}
I have a structure in my angular directive with promise.
How can we write test for the above promise.
When unit testing Angular, all promises are triggered to complete when you call $scope.$digest(). The timing is then up to you. The basic steps go:
Prep your test variables and other structures
Call the code that sets up the promise.
Call $scope.$digest() to trigger the promise.
Test for your expected results.
Assuming you are using the jasmine testing framework.
I believe you will need to mock promises similar to what is done here about halfway down the page..
I am trying to stub a method using sinon, jasmine and $q.
I want that method to return my fake data.
The problem is that the defined then statement is never called and i can not figure out why.
This already is a simplified version but it still isn't working:
The stub is called
The console log Steven Stub is called gets called
None of the then callbacks are called
No error message
Here is my code
var p = {steven: function() {console.log('original steven');}},
pStub = sinon.stub(p, 'steven', function(){
console.log('Steven Stub is called');
var defer = $q.defer();
defer.resolve({item: 5});
return defer.promise;
});
var promise = p.steven();
promise.then(
function(data){console.log('Peter?');},
function(data) {console.log('ERROR?');},
function(data) {console.log('progress?');});
Any idea?
You need to call a digest in order to resolve a promise. In Angular 2.0 this will be fixed, (and Angular 1.2 is slightly better here than Angular 1.1) but in the meanwhile you have to call
$rootScope.$digest()
In order to cause the promises to resolve. This is because promises work via evalAsync. See this question to learn more about how the digest cycle interacts with $q promises lifecycle.
I do not want to have the $http mocked. Main reason is that I am writing some integration tests so I want to have all AJAX requests executed. Currently in my tests none gets triggered.
Any suggestion is more than welcomed.
The angular mocks provide the mocked $httpBackend when you are using Jasmine tests, but the actual $httpBackend still exists. You just need to tell the provider to use the original when you inject the service. It will look something like this in your test:
var original_http_backend = null;
angular.module('ng').config(['$httpBackendProvider', function($httpBackendProvider) {
original_http_backend = $httpBackendProvider;
}]);
beforeEach(module(function($provide) {
$provide.provider('$httpBackend', original_http_backend)
}));
It's also worth pointing out that this approach, in general, is a BAD way to test your front end code. This is because it adds a backend dependency, so you cannot isolate your front end behavior. It is preferred to mock out the response and test with that.
I did end up using this feature though, since our mock responses were developed on the back end for testing there, and I didn't want to repeat the objects. This method allowed me to use them.
If you really need to test async operations, then you can use Jasmine Async Support. It basically consists in creating blocks of runs() alongside with waitsFor. A simple example:
var $http, returned;
it('should evaluate real XHR request', function() {
// this will probably reside in your controller
$http.$get('/foo.json').success(function(data) { returned = data; });
waitsFor(function() {
return !!returned;
}, 'The $http didnt resolved in 1 second', 1000);
runs(function() {
expect(returned).toBe('The value you expect to be returned');
});
});
Each runs block will run in the specified order, and when a waitsFor happens, the runs block will wait until the previous waitsFor resolve to true to run.