unitarios unitarias unit test pruebas karma angularjs jasmine

angularjs - unitarias - test unitarios angular 5



¿Cómo pruebo un servicio AngularJS con Jasmine? (4)

El problema es que el método de fábrica, que crea una instancia del servicio, no se llama en el ejemplo anterior (solo la creación del módulo no crea una instancia del servicio).

Para que el servicio sea instanciado, angular.injector debe ser llamado con el módulo donde se define nuestro servicio. Entonces, podemos preguntarle al nuevo objeto inyector por el servicio y solo entonces cuando el servicio finalmente se crea una instancia.

Algo como esto funciona:

describe(''myService test'', function(){ describe(''when I call myService.one'', function(){ it(''returns 1'', function(){ var $injector = angular.injector([ ''myModule'' ]); var myService = $injector.get( ''myService'' ); expect( myService.one ).toEqual(1); }) }) });

Otra forma sería pasar el servicio a una función usando '' invoke '':

describe(''myService test'', function(){ describe(''when I call myService.one'', function(){ it(''returns 1'', function(){ myTestFunction = function(aService){ expect( aService.one ).toEqual(1); } //we only need the following line if the name of the //parameter in myTestFunction is not ''myService'' or if //the code is going to be minify. myTestFunction.$inject = [ ''myService'' ]; var myInjector = angular.injector([ ''myModule'' ]); myInjector.invoke( myTestFunction ); }) }) });

Y, finalmente, la forma ''correcta'' de hacerlo es usar '' inject '' y '' module '' en un bloque de jazmín '' beforeEach ''. Al hacerlo tenemos que darnos cuenta de que la función ''inyectar'' no está en el paquete angularjs estándar, sino en el módulo ngMock y que solo funciona con jazmín.

describe(''myService test'', function(){ describe(''when I call myService.one'', function(){ beforeEach(module(''myModule'')); it(''returns 1'', inject(function(myService){ //parameter name = service name expect( myService.one ).toEqual(1); })) }) });

(Hay una pregunta relacionada aquí: prueba de jazmín no ve módulo AngularJS )

Solo quiero probar un servicio sin un arranque de Angular.

He visto algunos ejemplos y el tutorial, pero no voy a ir a ningún lado.

Tengo solo tres archivos:

  • myService.js: donde defino un servicio AngularJS

  • test_myService.js: donde defino una prueba de Jasmine para el servicio.

  • specRunner.html: un archivo HTML con la configuración normal de jazmín y donde importo los otros dos archivos anteriores y el Jasmine, Angularjs y angular-mocks.js.

Este es el código del servicio (que funciona como se espera cuando no estoy probando):

var myModule = angular.module(''myModule'', []); myModule.factory(''myService'', function(){ var serviceImplementation = {}; serviceImplementation.one = 1; serviceImplementation.two = 2; serviceImplementation.three = 3; return serviceImplementation });

Como estoy intentando probar el servicio de forma aislada, debería poder acceder a él y verificar sus métodos. Mi pregunta es: ¿cómo puedo inyectar el servicio en mi prueba sin poner en marcha el AngularJS?

Por ejemplo, ¿cómo puedo probar el valor devuelto por un método del servicio con Jasmine de esta manera?

describe(''myService test'', function(){ describe(''when I call myService.one'', function(){ it(''returns 1'', function(){ myModule = angular.module(''myModule''); //something is missing here.. expect( myService.one ).toEqual(1); }) }) });


Necesitaba probar una directiva que requería otra directiva, Google Places Autocomplete , estaba debatiendo si debería burlarme de ella ... de todos modos, esto funcionó sin arrojar ningún error para la directiva que requería gPlacesAutocompletar.

describe(''Test directives:'', function() { beforeEach(module(...)); beforeEach(module(...)); beforeEach(function() { angular.module(''google.places'', []) .directive(''gPlacesAutocomplete'',function() { return { require: [''ngModel''], restrict: ''A'', scope:{}, controller: function() { return {}; } }; }); }); beforeEach(module(''google.places'')); });


Si bien la respuesta anterior probablemente funciona bien (no lo he probado :)), a menudo tengo que hacer más pruebas para no inyectarme en las pruebas. Agruparé los casos () en bloques de descripción y ejecutaré mi inyección en un beforeEach () o beforeAll () en cada bloque de descripción.

Robert también tiene razón en que dice que debe usar el inyector Angular $ para que las pruebas sean conscientes del servicio o la fábrica. Angular también usa este inyector en sus aplicaciones para indicarle a la aplicación qué hay disponible. Sin embargo, se puede llamar en más de un lugar, y también se puede llamar implícitamente en lugar de explícitamente. Notarás en mi ejemplo de archivo de prueba de especificaciones a continuación, el bloque beforeEach () llama implícitamente al inyector para que todo esté disponible para ser asignado dentro de las pruebas.

Volviendo a agrupar cosas y usar antes-bloques, aquí hay un pequeño ejemplo. Estoy creando un Cat Service y quiero probarlo, por lo que mi configuración simple para escribir y probar el servicio sería así:

app.js

var catsApp = angular.module(''catsApp'', [''ngMockE2E'']); angular.module(''catsApp.mocks'', []) .value(''StaticCatsData'', function() { return [{ id: 1, title: "Commando", name: "Kitty MeowMeow", score: 123 }, { id: 2, title: "Raw Deal", name: "Basketpaws", score: 17 }, { id: 3, title: "Predator", name: "Noseboops", score: 184 }]; }); catsApp.factory(''LoggingService'', [''$log'', function($log) { // Private Helper: Object or String or what passed // for logging? Let''s make it String-readable... function _parseStuffIntoMessage(stuff) { var message = ""; if (typeof stuff !== "string") { message = JSON.stringify(stuff) } else { message = stuff; } return message; } /** * @summary * Write a log statement for debug or informational purposes. */ var write = function(stuff) { var log_msg = _parseStuffIntoMessage(stuff); $log.log(log_msg); } /** * @summary * Write''s an error out to the console. */ var error = function(stuff) { var err_msg = _parseStuffIntoMessage(stuff); $log.error(err_msg); } return { error: error, write: write }; }]) catsApp.factory(''CatsService'', [''$http'', ''LoggingService'', function($http, Logging) { /* response: data, status, headers, config, statusText */ var Success_Callback = function(response) { Logging.write("CatsService::getAllCats()::Success!"); return {"status": status, "data": data}; } var Error_Callback = function(response) { Logging.error("CatsService::getAllCats()::Error!"); return {"status": status, "data": data}; } var allCats = function() { console.log(''# Cats.allCats()''); return $http.get(''/cats'') .then(Success_Callback, Error_Callback); } return { getAllCats: allCats }; }]); var CatsController = function(Cats, $scope) { var vm = this; vm.cats = []; // ======================== /** * @summary * Initializes the controller. */ vm.activate = function() { console.log(''* CatsCtrl.activate()!''); // Get ALL the cats! Cats.getAllCats().then( function(litter) { console.log(''> '', litter); vm.cats = litter; console.log(''>>> '', vm.cats); } ); } vm.activate(); } CatsController.$inject = [''CatsService'', ''$scope'']; catsApp.controller(''CatsCtrl'', CatsController);

Especificación: Controlador de Gatos

''use strict''; describe(''Unit Tests: Cats Controller'', function() { var $scope, $q, deferred, $controller, $rootScope, catsCtrl, mockCatsData, createCatsCtrl; beforeEach(module(''catsApp'')); beforeEach(module(''catsApp.mocks'')); var catsServiceMock; beforeEach(inject(function(_$q_, _$controller_, $injector, StaticCatsData) { $q = _$q_; $controller = _$controller_; deferred = $q.defer(); mockCatsData = StaticCatsData(); // ToDo: // Put catsServiceMock inside of module "catsApp.mocks" ? catsServiceMock = { getAllCats: function() { // Just give back the data we expect. deferred.resolve(mockCatsData); // Mock the Promise, too, so it can run // and call .then() as expected return deferred.promise; } }; })); // Controller MOCK var createCatsController; // beforeEach(inject(function (_$rootScope_, $controller, FakeCatsService) { beforeEach(inject(function (_$rootScope_, $controller, CatsService) { $rootScope = _$rootScope_; $scope = $rootScope.$new(); createCatsController = function() { return $controller(''CatsCtrl'', { ''$scope'': $scope, CatsService: catsServiceMock }); }; })); // ========================== it(''should have NO cats loaded at first'', function() { catsCtrl = createCatsController(); expect(catsCtrl.cats).toBeDefined(); expect(catsCtrl.cats.length).toEqual(0); }); it(''should call "activate()" on load, but only once'', function() { catsCtrl = createCatsController(); spyOn(catsCtrl, ''activate'').and.returnValue(mockCatsData); // *** For some reason, Auto-Executing init functions // aren''t working for me in Plunkr? // I have to call it once manually instead of relying on // $scope creation to do it... Sorry, not sure why. catsCtrl.activate(); $rootScope.$digest(); // ELSE ...then() does NOT resolve. expect(catsCtrl.activate).toBeDefined(); expect(catsCtrl.activate).toHaveBeenCalled(); expect(catsCtrl.activate.calls.count()).toEqual(1); // Test/Expect additional conditions for // "Yes, the controller was activated right!" // (A) - there is be cats expect(catsCtrl.cats.length).toBeGreaterThan(0); }); // (B) - there is be cats SUCH THAT // can haz these properties... it(''each cat will have a NAME, TITLE and SCORE'', function() { catsCtrl = createCatsController(); spyOn(catsCtrl, ''activate'').and.returnValue(mockCatsData); // *** and again... catsCtrl.activate(); $rootScope.$digest(); // ELSE ...then() does NOT resolve. var names = _.map(catsCtrl.cats, function(cat) { return cat.name; }) var titles = _.map(catsCtrl.cats, function(cat) { return cat.title; }) var scores = _.map(catsCtrl.cats, function(cat) { return cat.score; }) expect(names.length).toEqual(3); expect(titles.length).toEqual(3); expect(scores.length).toEqual(3); }); });

Spec: Servicio de gatos

''use strict''; describe(''Unit Tests: Cats Service'', function() { var $scope, $rootScope, $log, cats, logging, $httpBackend, mockCatsData; beforeEach(module(''catsApp'')); beforeEach(module(''catsApp.mocks'')); describe(''has a method: getAllCats() that'', function() { beforeEach(inject(function($q, _$rootScope_, _$httpBackend_, _$log_, $injector, StaticCatsData) { cats = $injector.get(''CatsService''); $rootScope = _$rootScope_; $httpBackend = _$httpBackend_; // We don''t want to test the resolving of *actual data* // in a unit test. // The "proper" place for that is in Integration Test, which // is basically a unit test that is less mocked - you test // the endpoints and responses and APIs instead of the // specific service behaviors. mockCatsData = StaticCatsData(); // For handling Promises and deferrals in our Service calls... var deferred = $q.defer(); deferred.resolve(mockCatsData); // always resolved, you can do it from your spec // jasmine 2.0 // Spy + Promise Mocking // spyOn(obj, ''method''), (assumes obj.method is a function) spyOn(cats, ''getAllCats'').and.returnValue(deferred.promise); /* To mock $http as a dependency, use $httpBackend to setup HTTP calls and expectations. */ $httpBackend.whenGET(''/cats'').respond(200, mockCatsData); })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }) it('' exists/is defined'', function() { expect( cats.getAllCats ).toBeDefined(); expect( typeof cats.getAllCats ).toEqual("function"); }); it('' returns an array of Cats, where each cat has a NAME, TITLE and SCORE'', function() { cats.getAllCats().then(function(data) { var names = _.map(data, function(cat) { return cat.name; }) var titles = _.map(data, function(cat) { return cat.title; }) var scores = _.map(data, function(cat) { return cat.score; }) expect(names.length).toEqual(3); expect(titles.length).toEqual(3); expect(scores.length).toEqual(3); }) }); }) describe(''has a method: getAllCats() that also logs'', function() { var cats, $log, logging; beforeEach(inject( function(_$log_, $injector) { cats = $injector.get(''CatsService''); $log = _$log_; logging = $injector.get(''LoggingService''); spyOn(cats, ''getAllCats'').and.callThrough(); } )) it(''that on SUCCESS, $logs to the console a success message'', function() { cats.getAllCats().then(function(data) { expect(logging.write).toHaveBeenCalled(); expect( $log.log.logs ).toContain(["CatsService::getAllCats()::Success!"]); }) }); }) });

EDITAR Basándome en algunos de los comentarios, he actualizado mi respuesta para que sea un poco más compleja, y también inventé un Plunkr que demuestra las pruebas unitarias. Específicamente, uno de los comentarios mencionó "¿Qué pasa si el Servicio de un Controlador tiene una dependencia simple, como $ log?" - que se incluye en el ejemplo con casos de prueba. ¡Espero eso ayude! ¡Prueba o piratea el planeta!

https://embed.plnkr.co/aSPHnr/


Si quieres probar un controlador, puedes inyectarlo y probarlo de la siguiente manera.

describe(''When access Controller'', function () { beforeEach(module(''app'')); var $controller; beforeEach(inject(function (_$controller_) { // The injector unwraps the underscores (_) from around the parameter names when matching $controller = _$controller_; })); describe(''$scope.objectState'', function () { it(''is saying hello'', function () { var $scope = {}; var controller = $controller(''yourController'', { $scope: $scope }); expect($scope.objectState).toEqual(''hello''); }); }); });