Eu tenho myService que usa myOtherService, que faz uma chamada remota, retornando promise:
angular.module('app.myService', ['app.myOtherService']) .factory('myService', [myOtherService, function(myOtherService) { function makeRemoteCall() { return myOtherService.makeRemoteCallReturningPromise(); } return { makeRemoteCall: makeRemoteCall }; } ])
Para fazer um teste de unidade para myService
, preciso zombar de myOtherService
, de modo que seu método makeRemoteCallReturningPromise()
retorne uma promise. É assim que eu faço:
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'); }); }));
Como você pode ver acima, a definição do meu mock depende de $q
, que eu tenho que carregar usando inject()
. Além disso, injetar o mock deve estar acontecendo no module()
, que deve estar chegando antes de inject()
. No entanto, o valor para simulação não é atualizado depois de alterá-lo.
Qual é a maneira correta de fazer isso?
Eu não tenho certeza porque o jeito que você fez isso não funciona, mas eu geralmente faço isso com a function spyOn
. Algo assim:
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'); }); }));
Lembre-se também que você precisará fazer uma chamada $digest
para que a function then
seja chamada. Veja a seção Teste da documentação do $ q .
——EDITAR——
Depois de ver mais de perto o que você está fazendo, acho que vejo o problema em seu código. No beforeEach
, você está definindo myOtherServiceMock
para um object totalmente novo. O $provide
nunca verá esta referência. Você só precisa atualizar a referência existente:
beforeEach(inject( function(_myService_, $q){ myService = _myService_; myOtherServiceMock.makeRemoteCallReturningPromise = function() { var deferred = $q.defer(); deferred.resolve('Remote call result'); return deferred.promise; }; }
Também podemos escrever a implementação do jasmine de retornar a promise diretamente pelo espião.
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
Para Jasmine 2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(copiado de comentários, graças a ccnokes)
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(); }); });
Você pode usar uma biblioteca de arranhões como sinon para zombar de seu serviço. Você pode retornar $ q.when () como sua promise. Se o valor do seu object de escopo vier do resultado da promise, você precisará chamar o escopo. $ Root. $ Digest ().
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); });
usando sinon
:
const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled') .returns(httpPromise(200));
Conhecido que, httpPromise
pode ser:
const httpPromise = (code) => new Promise((resolve, reject) => (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true }) );
Honestamente .. você está indo sobre isso da maneira errada, contando com injetar para zombar de um serviço em vez de módulo. Além disso, chamar injetar em um beforeEach é um antipadrão, pois dificulta o uso de mocking por teste.
Aqui está como eu faria isso …
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; }; }); });
Agora, quando você injetar seu serviço, ele terá um método apropriadamente ridicularizado para uso.
Eu encontrei essa function de serviço útil, stabbing como sinon.stub (). Retorna ($ 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 );
controlador:
this.someMethod = function(someObj) { myService.myFunction( someObj ).then( function() { someObj.loaded = 'bla-bla'; }, function() { // failure } ); };
e teste
const obj = { field: 'value' }; this.ctrl.someMethod( obj ); this.scope.$digest(); expect( this.myService.myFunction ).toHaveBeenCalled(); expect( obj.loaded ).toEqual( 'bla-bla' );
O trecho de código:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() { var deferred = $q.defer(); deferred.resolve('Remote call result'); return deferred.promise; });
Pode ser escrito de uma forma mais concisa:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() { return $q.resolve('Remote call result'); });