Comment puis-je me moquer d'un service qui renvoie promise dans Angularjs Jasmine unit test?

J'ai myService qui utilise myOtherService, qui effectue un appel distant, renvoyant promise:

angular.module('app.myService', ['app.myOtherService'])
  .factory('myService', [myOtherService,

    function(myOtherService) {
      function makeRemoteCall() {
        return myOtherService.makeRemoteCallReturningPromise();
      }

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

Pour faire un test unitaire pour myService j'ai besoin de me moquer de myOtherService, de sorte que sa méthode makeRemoteCallReturningPromise() renvoie une promesse. Voici comment je le fais:

describe('Testing remote call returning promise', function() {
  var myService;
  var myOtherServiceMock = {};

  beforeEach(module('app.myService'));

  // I have to inject mock when calling module(),
  // and module() should come before any inject()
  beforeEach(module(function ($provide) {
    $provide.value('myOtherService', myOtherServiceMock);
  }));

  // However, in order to properly construct my mock
  // I need $q, which can give me a promise
  beforeEach(inject( function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock = {
      makeRemoteCallReturningPromise: function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;
      }    
    };
  }

  // Here the value of myOtherServiceMock is not
  // updated, and it is still {}
  it('can do remote call', inject(function() {
    myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
      .then(function() {
        console.log('Success');
      });    
  }));  

Comme vous pouvez le voir ci-dessus, la définition de ma simulation dépend de $q, que je dois charger en utilisant inject(). De plus, l'injection du simulacre devrait se faire dans module(), qui devrait venir avant inject(). Cependant, la valeur de mock n'est pas mis à jour une fois que je le change.

Quelle est la bonne façon de le faire?

139
demandé sur Georgii Oleinikov 2014-05-17 01:47:05

8 réponses

Je ne sais pas pourquoi la façon dont vous l'avez fait ne fonctionne pas, mais je le fais habituellement avec la fonction spyOn. Quelque chose comme ceci:

describe('Testing remote call returning promise', function() {
  var myService;

  beforeEach(module('app.myService'));

  beforeEach(inject( function(_myService_, myOtherService, $q){
    myService = _myService_;
    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;
    });
  }

  it('can do remote call', inject(function() {
    myService.makeRemoteCall()
      .then(function() {
        console.log('Success');
      });    
  }));

Rappelez-vous également que vous devrez faire un appel $digest pour que la fonction then soit appelée. Voir la section Testing de la documentation $ q .

------MODIFIER------

Après avoir regardé de plus près ce que vous faites, je pense que je vois le problème dans votre code. Dans le beforeEach, vous définissez myOtherServiceMock sur un nouvel objet entier. Le $provide ne verra jamais cette référence. Vous avez juste besoin de mettre à jour la référence existante:

beforeEach(inject( function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock.makeRemoteCallReturningPromise = function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;   
    };
  }
169
répondu dnc253 2015-05-28 11:51:19

Nous pouvons également écrire l'implémentation de jasmine de returning promise directement par spy.

spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));

Pour Jasmine 2:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));

(copié à partir des commentaires, grâce à ccnokes)

68
répondu Priya Ranjan Singh 2015-05-14 17:11:28
describe('testing a method() on a service', function () {    

    var mock, service

    function init(){
         return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
                mock = $injector.get('service_that_is_being_mocked');;                    
                service = __serviceUnderTest_;
            });
    }

    beforeEach(module('yourApp'));
    beforeEach(init());

    it('that has a then', function () {
       //arrange                   
        var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
            return {
                then: function (callback) {
                    return callback({'foo' : "bar"});
                }
            };
        });

        //act                
        var result = service.actionUnderTest(); // does cleverness

        //assert 
        expect(spy).toHaveBeenCalled();  
    });
});
11
répondu Darren Corbett 2015-04-28 07:15:39

Vous pouvez utiliser une bibliothèque de stubbing comme sinon pour se moquer de votre service. Vous pouvez ensuite retourner $ Q. when () comme votre promesse. Si la valeur de votre objet scope provient du résultat de la promesse, vous devrez appeler scope.$racine.$digérer().

var scope, controller, datacontextMock, customer;
  beforeEach(function () {
        module('app');
        inject(function ($rootScope, $controller,common, datacontext) {
            scope = $rootScope.$new();
            var $q = common.$q;
            datacontextMock = sinon.stub(datacontext);
            customer = {id:1};
           datacontextMock.customer.returns($q.when(customer));

            controller = $controller('Index', { $scope: scope });

        })
    });


    it('customer id to be 1.', function () {


            scope.$root.$digest();
            expect(controller.customer.id).toBe(1);


    });
8
répondu Mike Lunn 2015-01-15 02:43:39

En utilisant sinon:

const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
                     .returns(httpPromise(200));

Connu que, httpPromise peut être :

const httpPromise = (code) => new Promise((resolve, reject) =>
  (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);
2
répondu Abdennour TOUMI 2016-11-18 09:49:37

Honnêtement.. vous allez à ce sujet dans le mauvais sens en s'appuyant sur inject pour se moquer d'un service au lieu de module. En outre, appeler inject dans un beforeEach est un anti-modèle car il rend difficile la moquerie par test.

Voici comment je ferais cela...

module(function ($provide) {
  // By using a decorator we can access $q and stub our method with a promise.
  $provide.decorator('myOtherService', function ($delegate, $q) {

    $delegate.makeRemoteCallReturningPromise = function () {
      var dfd = $q.defer();
      dfd.resolve('some value');
      return dfd.promise;
    };
  });
});

Maintenant, lorsque vous injectez votre service, il aura une méthode correctement moquée pour l'utilisation.

0
répondu Keith Loy 2015-03-09 03:05:08

J'ai trouvé cette fonction de service utile et poignardante sinon.moignon().retourne ($Q. when ({})):

this.myService = {
   myFunction: sinon.stub().returns( $q.when( {} ) )
};

this.scope = $rootScope.$new();
this.angularStubs = {
    myService: this.myService,
    $scope: this.scope
};
this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );

Contrôleur:

this.someMethod = function(someObj) {
   myService.myFunction( someObj ).then( function() {
        someObj.loaded = 'bla-bla';
   }, function() {
        // failure
   } );   
};

Et test

const obj = {
    field: 'value'
};
this.ctrl.someMethod( obj );

this.scope.$digest();

expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );
0
répondu Dmitri Algazin 2017-04-19 17:27:20

L'extrait de code:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
    var deferred = $q.defer();
    deferred.resolve('Remote call result');
    return deferred.promise;
});

Peut être écrit sous une forme plus concise:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
    return $q.resolve('Remote call result');
});
0
répondu trunikov 2017-05-12 19:42:23