rattanak
rattanak

Reputation: 1508

Testing Angular Service that returns a promise

I use Karma + Jasmine to write Unit Test for an angular project.

I am trying to test an Angular service that returns the correct result via a promise. Below are what I have so far.

Project Service

'use strict';

var app = app || {};

app.factory('ProjectSvc', ['$http', '$q', function($http, $q) {

    var project_url = 'https://dl.dropboxusercontent.com/u/2122820/hosted_json/project.json';

    var svc = {};
    svc.cachedProjects = [];

    svc.getProjects = function() {
        return $http.get(project_url);
    }

    function lookUp(slug) {
        for (var i=0; i < svc.cachedProjects.length; i++){
            if (svc.cachedProjects[i].slug && svc.cachedProjects[i].slug == slug) {
                return svc.cachedProjects[i];
            } else if (svc.cachedProjects[i].title == slug) {
                return svc.cachedProjects[i];
            
            }
        }
        return null;
    }

    //return a promise
    svc.retrieveProjectById = function(id) {
        var deferred = $q.defer();
        if (svc.cachedProjects.length > 0 ) {    
             var found = lookUp(id);
                if (!found) {
                    deferred.reject ({error: 'not found', data: null});
                } else {
                    deferred.resolve(found);
                }
        } else {
            init().then(function(data){
                //console.log('---------');
                svc.cachedProjects = data.data.projects;

                var found = lookUp(id);
                //console.log('found: ', found);
                if (!found) {
                    deferred.reject ({error: 'not found', data: null});
                } else {
                     deferred.resolve(found);
                }
            })
        }
        return deferred.promise;  
    }

     //return a promise
    svc.retrieveProjectBySlug = function(slug) {
       return svc.retrieveProjectById(slug)
    }


    function init(index) {
    	console.log('init');
        return svc.getProjects().then(function(data) {
            svc.cachedProjects = data.data.projects;
        });
    }





    return svc;



}]);

describe('Project Service', function() {
  beforeEach(module('myApp'));
  var ProjectSvc, $httpBackend;
  var $q, $rootScope;

  beforeEach(inject(function(_ProjectSvc_, _$httpBackend_, _$q_, _$rootScope_) {
    // The injector unwraps the underscores (_) from around the parameter names when matching
    ProjectSvc = _ProjectSvc_;
    $httpBackend = _$httpBackend_;
    $q = _$q_;

    $rootScope = _$rootScope_;

    spyOn(ProjectSvc, 'retrieveProjectBySlug').and.callFake(function() {
      var defer = $q.defer();
      defer.resolve();
      return defer.promise;
    });
  }));


  describe('.retrieveProjectBySlug()', function() {
    it('test promise in service', function() {

      var project_url = 'https://dl.dropboxusercontent.com/u/2122820/hosted_json/project.json';
      //$httpBackend.expectGET(project_url).respond(200, {data: null});

      console.log('-------------');
      var project;
      ProjectSvc.retrieveProjectBySlug('monopoly-checker').then(function(data) {
        project = data;
        console.log(project);
        $rootScope.$apply();
        console.log('project', project);

      });

      expect(project.length).toBe(1);
    });
  });
});
http://stackoverflow.com/questions/ask#

Running karma start, I get:

1) test promise in service
 Project Service .retrieveProjectBySlug()
 TypeError: Cannot read property 'length' of undefined

It seems that the promise never gets resolved, thus project is always undefined. Can you fix this ?

If it helps, full source code is at https://github.com/rattanakchea/rattanakchea.github.io/tree/test

Upvotes: 0

Views: 852

Answers (1)

Estus Flask
Estus Flask

Reputation: 222498

$rootScope.$apply() should be called outside of the promise:

  ProjectSvc.retrieveProjectBySlug('monopoly-checker').then(function(data) {
    project = data;
    console.log(project);
    console.log('project', project);
  });

  $rootScope.$apply();

$q promises are tied to digest cycle, promise chain is called on digest.

Consider using Jasmine promise matchers, they eliminate the need for unwrapping $q promises in specs.

Upvotes: 1

Related Questions