Stephane
Stephane

Reputation: 12750

Trying to supply a promise to a service for an AngularJS unit test

I'm trying to unit test a factory:

angular.module('app.user').factory('UserService',
  ['RESTService', 'RESTHTTPService',
  function(RESTService, RESTHTTPService) {
    var factory = {};

    factory.search = function(searchTerm, page, size, sort, callback) {
      return RESTService.User.search({searchTerm: searchTerm, page: page, size: size, sort: sort}).$promise.then(callback);
    };

    return factory;
  }
]);

So I needed to mock its RESTService dependency.

angular.module('app.user.mock').factory('RESTService',
  ['$q',
  function($q) {
    var factory = { User: {} };

    factory.allUsers = {
      content: [ { firstname: 'Spirou', lastname: 'Fantasio', email: '[email protected]', workPhone: '983743464365' } ],
      page: '1'
    };

    factory.User.search = function() {
      var defer = $q.defer();
      defer.resolve(factory.allUsers);
      return defer;
    };

    return factory;
  }
]);

To be used in my unit test:

describe('user service repository', function() {

  beforeEach(function() {
    module('com.nsn.nitro.project');
    module('app.utils.mock');
  });

  var UserService;
  var RESTService;

  beforeEach(inject(function(_UserService_, _RESTService_) {
    UserService = _UserService_;
    RESTService = _RESTService_;
  }));

  it('should return the list of users', function () {
    var users = null;
    UserService.search('TOTO', 1, 10, 'asc', function(data) {
      users = data.content;
    });
    expect(users).toEqual(RESTService.allUsers.content);
  });

});

But it gives me the error:

TypeError: Cannot read property 'then' of undefined
        at Object.factory.all (/home/stephane/dev/js/projects/angularjs/nitro-project/app/modules/user/service/repository.js:29:81)
        at null.<anonymous> (/home/stephane/dev/js/projects/angularjs/nitro-project/test/spec/modules/user/service.js:59:17)

The mocked RESTService is injected fine in the UserService but the promise property, or rather the $promise property, is undefined.

As the code works fine in production, I would like not to touch the existing UserService and RESTService factories. If possible.

Upvotes: 0

Views: 146

Answers (2)

Stephane
Stephane

Reputation: 12750

To get the test to pass I needed to do two things:

1- Add a $rootScope.$digest(); right after the service call:

  it('should return the list of searched users', function() {
    var users = null;
    UserService.search('TOTO', 1, 10, 'asc', function(data) {
      users = data.content;
    });
    $rootScope.$digest();
    expect(users).toEqual(RESTService.allUsers.content);
  });

2- Add $httpBackend.whenGET(/.html$/).respond(''); to work around the issue of httpBackend generating a call to the ui-router template. I'm discussing this issue in another thread at How to avoid the 'Error: Unexpected request: GET' every time an AngularJS test does a rootScope.digest() or httpBackend.flush()

  beforeEach(inject(function(_$httpBackend_, _$rootScope_, _UserService_, _RESTService_) {
    $httpBackend = _$httpBackend_;
    $rootScope = _$rootScope_;
    UserService = _UserService_;
    RESTService = _RESTService_;

    // Always use this statement so as to avoid the error from the $http service making a request to the application main page
    $httpBackend.whenGET(/\.html$/).respond('');
  }));

Upvotes: 0

eladcon
eladcon

Reputation: 5825

$q's promise doens't have a property named $promise. You can set a one with a reference to the promise property:

factory.User.get = function() {
  var defer = $q.defer();
  defer.resolve(this.allUsers);
  defer.$promise = defer.promise;
  return defer;
};

Also, because the test is async, you might need to use jasmine's done parmater (depends on your version):

it('should return the list of users', function (done) {
    var users = null;
    UserService.all(1, 10, 'asc', function(data) {
      users = data.content;
      expect(users).toEqual(RESTService.allUsers.content);
      done();
    });      
});

Upvotes: 1

Related Questions