Monday, May 20, 2024
27
rated 0 times [  28] [ 1]  / answers: 1 / hits: 19074  / 11 Years ago, sun, february 24, 2013, 12:00:00

I am using jasmine to unit test an angularjs controller that sets a variable on the scope to the result of calling a service method that returns a promise object:



var MyController = function($scope, service) {
$scope.myVar = service.getStuff();
}


inside the service:



function getStuff() {
return $http.get( 'api/stuff' ).then( function ( httpResult ) {
return httpResult.data;
} );
}


This works fine in the context of my angularjs application, but does not work in the jasmine unit test. I have confirmed that the then callback is executing in the unit test, but the $scope.myVar promise never gets set to the return value of the callback.



My unit test:



describe( 'My Controller', function () {
var scope;
var serviceMock;
var controller;
var httpBackend;

beforeEach( inject( function ( $rootScope, $controller, $httpBackend, $http ) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
serviceMock = {
stuffArray: [{
FirstName: Robby
}],

getStuff: function () {
return $http.get( 'api/stuff' ).then( function ( httpResult ) {
return httpResult.data;
} );
}
};
$httpBackend.whenGET( 'api/stuff' ).respond( serviceMock.stuffArray );
controller = $controller( MyController, {
$scope: scope,
service: serviceMock
} );
} ) );

it( 'should set myVar to the resolved promise value',
function () {
httpBackend.flush();
scope.$root.$digest();
expect( scope.myVar[0].FirstName ).toEqual( Robby );
} );
} );


Also, if I change the controller to the following the unit test passes:



var MyController = function($scope, service) {
service.getStuff().then(function(result) {
$scope.myVar = result;
});
}


Why is the promise callback result value not being propagated to $scope.myVar in the unit test? See the following jsfiddle for full working code http://jsfiddle.net/s7PGg/5/


More From » unit-testing

 Answers
9

I guess that the key to this mystery is the fact that AngularJS will automatically resolve promises (and render results) if those used in an interpolation directive in a template. What I mean is that given this controller:



MyCtrl = function($scope, $http) {
$scope.promise = $http.get('myurl', {..});
}


and the template:



<span>{{promise}}</span>


AngularJS, upon $http call completion, will see that a promise was resolved and will re-render template with the resolved results. This is what is vaguely mentioned in the $q documentation:




$q promises are recognized by the templating engine in angular, which
means that in templates you can treat promises attached to a scope as
if they were the resulting values.




The code where this magic happens can be seen here.



BUT, this magic happens only when there is a template ($parse service, to be more precise) at play. In your unit test there is no template involved so promise resolution is not propagated automatically.



Now, I must say that this automatic resolution / result propagation is very convenient but might be confusing, as we can see from this question. This is why I prefer to explicitly propagate resolution results as you did:



var MyController = function($scope, service) {
service.getStuff().then(function(result) {
$scope.myVar = result;
});
}

[#80039] Friday, February 22, 2013, 12 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
jocelynkarsynr

Total Points: 472
Total Questions: 98
Total Answers: 96

Location: Macau
Member since Mon, Nov 16, 2020
4 Years ago
jocelynkarsynr questions
Tue, Feb 8, 22, 00:00, 2 Years ago
Sat, Jul 11, 20, 00:00, 4 Years ago
Sun, May 10, 20, 00:00, 4 Years ago
Sat, Jan 18, 20, 00:00, 4 Years ago
;