Proper testing of promises tied to $ scope

According to angular.js source:

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

So, I have a controller that gets a list of categories from the backend,

function myController($scope, $categoryService) { ... $scope.categoriesList = categoryService.search().then(function(response) { return response; } ... } 

and in my template I have a choice:

 <select multiple ng-model="categories" ng-options="category.name for category in categoriesList"></select> 

which "works" in the browser (selection shows a populated list)

but how do you test it?

I have the following specification:

 it('populates the categoriesList from the categoryService', inject(function(categoryService, $q, $controller, $rootScope) { var $scope = $rootScope.$new(); var catList = [{id:1, name:"Animal"},{id:2, name:"Vegetable"}]; var deferred = $q.defer() spyOn(categoryService, 'search').andReturn(deferred.promise); $controller(myController, {'$scope': $scope}); expect(categoryService.search).toHaveBeenCalled(); // PASSES deferred.resolve(catList); $scope.$digest(); expect($scope.categoriesList).toEqual(catList); // FAILS, returns the promise instead })); 

If I rewrote my initializer like this

 ...then(function(response) { $scope.categoriesList = response; } 

My tests will pass, but then I will not assign a promise to the field, and the template engine does not solve the promise for me. It seems to me that the first implementation is what the framework provides, but this cannot be verified. The second implementation may be a validation, but not the intended way of attaching data to a region.

+4
source share
2 answers

When you speak

 $scope.categoriesList = categoryService.search().then(function(response) { return response; } 

$scope.categoriesList not assigned response ; instead, it is assigned a new promise that resolves to response (as indicated in your test). Since the initial promise is already resolved by response , you can simply:

 $scope.categoriesList = categoryService.search() 

What the documents mean, you can assign $scope.categoriesList for such a promise, and the view will consider the categoriesList expression as the value that it resolves to (in this case, response ) - it doesn This value is actually accepted and assigns it to the scope.

[Update]

If you are testing the controller, and not the category itself, I would leave this promise completely - maybe something like this:

 it('populates the categoriesList from the categoryService', inject(function(categoryService, $controller, $rootScope) { var $scope = $rootScope.$new(); var catList = [{id:1, name:"Animal"},{id:2, name:"Vegetable"}]; spyOn(categoryService, 'search').andReturn(catList); $controller(myController, {'$scope': $scope}); expect(categoryService.search).toHaveBeenCalled(); $scope.$digest(); expect($scope.categoriesList).toEqual(catList); })); 
+1
source

I had the same problem and ended up like in my test:

 it("leaves a 'countries' promise that resolves to countries in the scope", function() { var value = null; scope.countries.then(function(v) { value = v; }); scope.$apply(); expect(value).toEqual([{ Code: "SE", Name: "Sweden" }]); }); 

Countries instead of categories, but the same technique should work in your case.

(I skipped beforeEach, which creates a scope variable and sets the GET to wait with $ httpBackend, but they are, of course, necessary.)

+1
source

All Articles