How to check if object is promise in Angular unit tests? - angularjs

I want to have an assert that checks if the returned object from calling a method is a promise.
Is it enough to check if it exists and has a then method?
Or is there a better way of doing this since having a then method doesn't guarantee the object is a $q deferred promise?
EDIT:
The question is not duplicate of "Any way to know if a variable is an angularjs promise?".
The solution for that question is to ensure that a promise is returned using $q.when().
I'm asking how to know that an object is a promise, not how to make it one if it isn't.
Thanks.

As far as I can tell, there is no way to do this directly.
When constructing objects, i normally do it like this:
function Derp(){
this.constructor = Derp;
}
Derp.prototype.method = function(){};
So that I can:
expect( new Derp() instanceof Derp ).toBe(true);
but in both $q.promise and $q itself, there are no constructor or prototype properties except for the constructor property in their __proto__ (in chrome) objects; and even those just point to JavaScripts' fundamental Object.
So in order to test that something is a promise, you have to make the assumption that the object you're returning is a promise based on its' other properties:
expect(typeof result.when).toBe('function');
expect(typeof result.then).toBe('function');
I don't like it either, but that's the only way I can think of to do this.

Related

Unit-testing ngResource $save

I have a resource which wraps a RESTful API. I use that resource from my controller to create new objects and save them, similar to this snippet from the Angular docs:
var newCard = new CreditCard({number:'0123'});
newCard.name = "Mike Smith";
newCard.$save();
When writing a unit test in Jasmine, I get the following error when the $save call is executed: "Cannot read property '$promise' of undefined".
What's the best approach to testing the method in my controller which contains the above code?
If you use Jasmine's spyOn() function to verify that a $resource method is called, it overwrites the original $resource method with one that implements the "spying" functionality.
If the code in your application relies on the $resource setting the $promise property, or it relies on the returned object/array from the $resource, the Jasmine's spy function won't return anything or set a value on the $promise property. As a result, perfectly fine code in your application will fail when being tested. A similar thing happens when you use $http with the then(), success(), or error() functions.
To work around that you can make Jasmine spy on the function as well as call the original function by doing something like this:
// Newer Jasmine 2.0 syntax:
spyOn(resource, "$save").and.callThrough();
// Older syntax:
spyOn(resource, "$save").andCallThrough();

Updating 'this' Context Property Inside of a $Promise In An Angular JS Service

I have a function being used in my service that is defined as:
var getData = function() {
return anotherService.getData().$promise;
};
and a this property that I manipulate throughout the service.
this.someProperty = 'a string';
I call the above function inside the return section of my service:
return{
updateProperty: function(){
getData().then(function(data){
this.someProperty = data;
});
}
}
In the above, I get an this is undefined related error in my browser console. I assume this is because the resolved $promise is an AJAX call and this is used out of context. What's the best way to manipulate a this property using the returned data from an AJAX call in this instance?
if you're manipulating this throughout your service, assign it to a variable like var self = this. The problem is that this is the context of a function and can be changed by other code using fn.call(context) or fn.apply(context, args). So it's liable to be different inside of the scope of any given function.
So just assign it to some variable and use it:
var self = this;
return {
updateProperty: function(){
getData().then(function(data){
self.someProperty = data;
});
}
};
The simplest way would be to use the bind function. This function sets the 'this' context for the function call. So, in your case you'd have to use it twice so that the proper 'this' populates in.
return{
updateProperty: function(){
getData().then((function(data){
this.someProperty = data;
}).bind(this));
}
}
This comes to ensure that the handler you passed to the promise is executed with the original 'this' (passed to updateProperty). Now, to pass the correct 'this' value to the updateProperty function, you should, in your controller do:
(myService.updateProperty.bind(this))();
There are numerous versions of binding, including binding the entire service. Also, have a look at lodash for function extensions.
I prepared a small pen to demonstrate this. It covers what I listed above, plus another important thing to note. When you use setTimeout, the handler is invoked with in the global context (in this case, 'window'), this is why I added a third bind, to make sure 'this' is relevant inside the timeout handler. I also added various count increment calls to demonstrate that 'this' is the same value along the way.
If this is a repeating scenario, you might want to pass either the target object (and then use the handler just to know it was updated), or a handler (which also needs binding). I added examples for these scenarios as well.
One last word, call, apply and bind are key to javascript and worth learning. Put some time into it and work your way out of context hell.

Any way to know if a variable is an angularjs promise?

I'm making a directive that takes a function as a scope parameter (scope: { method:'&theFunction' }). I need to know if the result returned by that method is an angular promise (if yes something will happen on resolution, otherwise it happens right away).
For now I'm testing if foo.then exists but I was wondering if there was a better way to do it.
You can use $q.when to wrap the object as a promise (whether it is or not). Then, you can be sure that you are always dealing with a promise. This should simplify the code that then handles the result.
Documentation for $q.when is here with $q.
Angular's when() is a good option as Davin mentioned.
If that doesn't meet your needs then Angular's internal way of checking (it uses this inside when) is very close to what you're doing:
var ref = function(value) {
if (value && isFunction(value.then)) {
// Then this is promise
}
#kayakDave, thanks for guiding to right place.
angular $q
when(value, [successCallback], [errorCallback], [progressCallback]);
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.
$q.when(value).then(function (data) {
//this helps me to bind data from $resource or $http or object
}
check this fiddle
The $q.when() answer seems like the best answer for most use cases, I used instanceof for mine.
if(buttonData instanceof $q) {
buttonData.then(function(actions) {
$scope.buttonActions = actions;
});
} else {
$scope.button = buttonData;
}
Alternatively, the following IF worked as well, but I ended up going with the above solution.
if(Object.getPrototypeOf(buttonData) === $q.prototype) {

AngularJS - binding/watching a function which returns a promise

I posted an issue on the AngularJS github but it doesn't seem to be getting a whole lot of attention and I wasn't able to fix it myself since it's a pretty low-level issue, so I think it's time to look for a workaround.
Angular allows you to put a promise (or anything with a .then(...) function) into your scope, and once it is resolved, all $watches and anything bound to that promise will use the resolved value. The issue arises when you use a function to return a promise, as the same doesn't apply - it's handled like a plain object.
For example:
var helloDef = $.Deferred();
$scope.hello = helloDef.promise();
$scope.getHello = function() {
return $scope.hello;
};
$timeout(function() {
helloDef.resolve('Hello!');
}, 1000);
Fiddle
Here using ng-bind="hello" works fine and outputs Hello!, but ng-bind="getHello()" outputs [object Object] as the internal $watch returns the promise object. Works the same way with $q instead of $.Deferred.
In my actual code I'm creating the promise the first time the function is called, so I can't just pre-make the promise and refer to that in scope.
I also need it for more than just ng-bind, so making my own binding directive which handles this case properly isn't viable.
Anyone have any ideas? I'm currently returning the promise if the data hasn't resolved and the actual result if it has, but that's a pain to work with. Anything bound to the data briefly causes weird side effects as the data is being loaded, like ngRepeat using the promise object instead of the resolved value to create elements.
Thanks.
UPDATE: Pull request: https://github.com/angular/angular.js/pull/3605
UPDATE 2: For future reference, automatic promise unwrapping has been deprecated in the 1.2 brach.
For some things, I use $resource. If I need to wait for it, $then works well:
var r = $resource...get...
var p = r.$then...
Otherwise, I build my own resource-like object that is not a promise, but has a promise that I can wait for.
Pull request merged, it should be fixed in 1.2.0 so I'll mark this as the answer.

How to get value of promise that already resolved?

I have a promise that I am binding to in my UI. When the promise resolves and the UI element renders I can then click on that UI element. In my controller code where I handle the click I would like to act on the value of the promise. At this point in my angular code I already know that the promise is resolved.. but when I want to get the value its still a promise.
Whats the best way to get the value of the promise... given that I know it must have resolved?
Promises are always promises - and that's how they should be. While it is possible to bind a promise directly to the view, I generally discourage this practice because it lacks transparency and can lead to view flickering. A better solution is to assign a scope value in a then call:
myService.then(function( val ) {
$scope.val = val;
});
When $scope.val is ready, it can always be treated directly as a value - because it is.
That said, if you want to do the promise assignment directly, you just have to treat it like a promise; that is, you need to call promise.then(function () { ... });. This seems like an inconvenience, but it's actually a testament to just how powerful promises are.
If you want some more info on promises, I pontificated on them (and provided code examples) for a recent Google+ Hangout, where I covered the advantages, common uses, and best practices of promises in AngularJS: AngularJS Hangout -- Promises, Promises
Promises, Promises. The idea of never-ending promise chains was specifically addressed, though I don't have a timecode off the top of my head.
Hope this helps!
What's tricky to understand is that you might think that if the Promise is not yet full-filled then maybe if I wait a bit I can test again to see if by now it has a value. But that is not possible because as explained on MDN:
"Callbacks will never be called before the completion of the current run of the JavaScript event loop."
So as long as your currently started function is running, the Promise can not be full-filled. It has no "value" during the current run of the event loop. "Promise" means "I promise to maybe give it to you during the NEXT event-loop run".
You could save that promise to a global variable and then when a user clicks on a button in the browser a new event-loop starts by running your event-handler which can put another "when()" onto the promise such that if the promise is resolved by that time the new when-function takes the value it gets as argument and saves it into a variable from which you can read it. No need to write it to console, to see that it is there.
That for me was the tricky part, I thought yes sure I can make the Promise write to the console, but I can't read from console, so how can I get that value into the rest of my program? Answer: Wait till the current event-loop is over, then you can.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Resources