unitarios unitarias test pruebas integracion hacer como javascript angularjs unit-testing mocking jasmine

javascript - unitarias - ¿Cómo me burlo de un servicio que promete en la prueba de unidad Angularjs Jasmine?



test unitarios angular 2 (8)

Tengo myService que usa myOtherService, que hace una llamada remota, que promete:

angular.module(''app.myService'', [''app.myOtherService'']) .factory(''myService'', [myOtherService, function(myOtherService) { function makeRemoteCall() { return myOtherService.makeRemoteCallReturningPromise(); } return { makeRemoteCall: makeRemoteCall }; } ])

Para hacer una prueba unitaria para myService , necesito myOtherService , de modo que su método makeRemoteCallReturningPromise() devuelva una promesa. Así es como lo hago:

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 puede ver en lo anterior, la definición de mi simulacro depende de $q , que debo cargar usando inject() . Además, inyectar el simulacro debería estar sucediendo en el module() , que debería estar llegando antes de la inject() . Sin embargo, el valor para el simulacro no se actualiza una vez que lo cambio.

¿Cuál es la forma apropiada de hacer esto?


El fragmento de código:

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

Puede escribirse de una forma más concisa:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() { return $q.resolve(''Remote call result''); });


Encontré esa útil función de servicio de apuñalamiento como sinon.stub (). Returns ($ 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 } ); };

y prueba

const obj = { field: ''value'' }; this.ctrl.someMethod( obj ); this.scope.$digest(); expect( this.myService.myFunction ).toHaveBeenCalled(); expect( obj.loaded ).toEqual( ''bla-bla'' );


Honestamente ... estás haciendo esto incorrectamente al confiar en inyectar para burlar un servicio en lugar de un módulo. Además, llamar a inyectar en un beforeEach es un antipatrón porque dificulta la burla por prueba.

Así es como yo haría esto ...

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; }; }); });

Ahora, cuando inyecte su servicio, tendrá un método de uso debidamente burlado.


No estoy seguro de por qué la forma en que lo hiciste no funciona, pero normalmente lo hago con la función spyOn . Algo como esto:

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''); }); }));

También recuerde que deberá realizar una llamada $digest para que se llame a la función. Vea la sección Pruebas de la documentación de $ q .

------EDITAR------

Después de mirar más de cerca lo que estás haciendo, creo que veo el problema en tu código. En beforeEach , está configurando myOtherServiceMock para un objeto completamente nuevo. El $provide nunca verá esta referencia. Solo necesita actualizar la referencia existente:

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


Puede usar una biblioteca de stubbing como sinon para burlarse de su servicio. A continuación, puede devolver $ q.when () como su promesa. Si el valor de su objeto de ámbito proviene del resultado de la promesa, deberá llamar a scope. $ 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); });


También podemos escribir la implementación de jazmín de devolver la promesa directamente por espía.

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

Para Jasmine 2:

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

(copiado de los comentarios, gracias a ccnokes)


usando sinon :

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

Sabido que, httpPromise puede ser:

const httpPromise = (code) => new Promise((resolve, reject) => (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true }) );


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(); }); });