User4567
User4567

Reputation: 1025

Mock promises in angularjs

I tried to mocking angular promises but I got some errors like undefined is not a function evaluating 'spyOn' fileUploadService

My controller code is

$scope.getUserFiles = function() {
    fileUploadService.retrieveUserFileNames('')
    .then(function(data) {
        $scope.userFiles =  data;
    });
};

service code, I call this method from my controller

this.retrieveUserFileNames= function(userId) {
    var deferred = $q.defer();
    $http({
        method : "GET",
        url : "/retrieveExcelSheetNames",
        params : {
            "userId" : userId
        }
    }).success(function(data) {
        deferred.resolve(data);
    }).error(function(data, status) {
        deferred.reject(data);
    });
    return deferred.promise;
};

test controller code

beforeEach(function() {
    inject(function(_fileUploadService_ , _$q_) {
        spyOn(scope, '$on');
        var deferred = _$q_.defer();
        fileUploadService = _fileUploadService_;
        deferred.resolve('resolveData');
        spyOn(fileUploadService, 'retrieveUserFileNames').andReturn(deferred.promise);
    });
});

it('is now a lot easier', function() {
   scope.getUserFiles();
   rootScope.$apply();
   expect(scope.userFiles).toBe('resolveData');
});

Thanks

Upvotes: 1

Views: 1102

Answers (1)

gkunz
gkunz

Reputation: 1423

As you're asynchronously set $scope.userFiles in your controller so you need to wait for the $apply from $http that resolved your promise.

Assuming you're using Jasmine you could use the asynchronous spec notation and register a $watch function to test your controller (Didn't test the code so you might need to tune it and it also depends on other modifications of the scope variable).

it('is now a lot easier', function(done) {
  scope.$watch('userFiles', function() {
     expect(scope.userFiles).toBe('resolveData');
     // Call done function to tell Jasmine that the spec has completed
     done();
  });

  scope.getUserFiles();
});

However, I think you should rather test your service and you can use $httpBackend from ngMock which is overriding $http and enables you to test much more in-depth. There you can test / spy the calls to the mock backend and also use Jasmines done() function to wait for the promise.

Here is a simple exmaple:

angular.module('mainApp', [])
  // Simple factory that uses $http for later use of $httpBackend mocking structure
  .factory('userManager', function($http, $q) {
     return $http.get('/api/users');
  });

And the specs:

describe('Simple factory that uses $http for later use of $httpBackend mocking structure', function() {
  // We need to use the module mainApp for all our tests
  beforeEach(module('mainApp'));

  // We use $httpBackend from ngMock module to mock our webservice call ($http will be overriden)
  // Also we need to inject inside of spec function as we need to use the async spec form with
  // a done function and this is not available using the inject to proxy the spec function
  it('should return desired user list', function(done) {

    inject(function($httpBackend, userManager) {

      $httpBackend.when('GET', '/api/users').respond({
        userList: [
          {user: 'User A'},
          {user: 'User B'},
          {user: 'User C'},
          {user: 'User D'},
        ]
      });
      $httpBackend.expectGET('/api/users');

      // userManager is returning a promise so we need to check asynchronously
      userManager.getUserList().then(function(result) {
        expect(result.data.userList).toContain(
          {user: 'User A'}, 
          {user: 'User B'}, 
          {user: 'User C'}, 
          {user: 'User D'}
        );

        // This call to the Jasmine done function is very important for asynchronous
        // unit tests as Jasmine is determining if the test is done by this call
        done();
      });

      $httpBackend.flush();

      $httpBackend.verifyNoOutstandingExpectation();
      $httpBackend.verifyNoOutstandingRequest();
    });
  });
});

Upvotes: 2

Related Questions