unit tutorial tests test karma and javascript unit-testing angularjs jasmine

javascript - tutorial - karma js



Angularjs promete no ser resuelto en la prueba de unidad (3)

Estoy usando jazmín para probar la unidad de un controlador angularjs que establece una variable en el alcance para el resultado de llamar a un método de servicio que devuelve un objeto de promesa:

var MyController = function($scope, service) { $scope.myVar = service.getStuff(); }

dentro del servicio:

function getStuff() { return $http.get( ''api/stuff'' ).then( function ( httpResult ) { return httpResult.data; } ); }

Esto funciona bien en el contexto de mi aplicación angularjs, pero no funciona en la prueba de la unidad de jazmín. He confirmado que la devolución de llamada "then" se está ejecutando en la prueba de unidad, pero la promesa $ scope.myVar nunca se establece en el valor de devolución de la devolución de llamada.

Mi prueba de unidad:

describe( ''My Controller'', function () { var scope; var serviceMock; var controller; var httpBackend; beforeEach( inject( function ( $rootScope, $controller, $httpBackend, $http ) { scope = $rootScope.$new(); httpBackend = $httpBackend; serviceMock = { stuffArray: [{ FirstName: "Robby" }], getStuff: function () { return $http.get( ''api/stuff'' ).then( function ( httpResult ) { return httpResult.data; } ); } }; $httpBackend.whenGET( ''api/stuff'' ).respond( serviceMock.stuffArray ); controller = $controller( MyController, { $scope: scope, service: serviceMock } ); } ) ); it( ''should set myVar to the resolved promise value'', function () { httpBackend.flush(); scope.$root.$digest(); expect( scope.myVar[0].FirstName ).toEqual( "Robby" ); } ); } );

Además, si cambio el controlador a lo siguiente, la unidad pasa la prueba:

var MyController = function($scope, service) { service.getStuff().then(function(result) { $scope.myVar = result; }); }

¿Por qué el valor del resultado de la devolución de la promesa no se propaga a $ scope.myVar en la prueba unitaria? Consulte el siguiente jsfiddle para ver el código de funcionamiento completo http://jsfiddle.net/s7PGg/5/


Supongo que la clave de este "misterio" es el hecho de que AngularJS resolverá automáticamente las promesas (y los resultados de representación) si se utilizan en una directiva de interpolación en una plantilla. Lo que quiero decir es que dado este controlador:

MyCtrl = function($scope, $http) { $scope.promise = $http.get(''myurl'', {..}); }

y la plantilla:

<span>{{promise}}</span>

AngularJS, al término de la llamada de $ http, "verá" que se ha resuelto una promesa y volverá a presentar la plantilla con los resultados resueltos. Esto es lo que se menciona vagamente en la documentación de $ q :

Las promesas de $ q son reconocidas por el motor de plantillas en angular, lo que significa que en las plantillas puede tratar las promesas adjuntas a un alcance como si fueran los valores resultantes.

El código donde ocurre esta magia se puede ver aquí .

PERO, esta "magia" solo ocurre cuando hay una plantilla (servicio $parse , para ser más preciso) en juego. En su prueba de unidad no hay plantilla involucrada, por lo que la resolución de la promesa no se propaga automáticamente .

Ahora, debo decir que esta resolución automática / propagación de resultados es muy conveniente, pero podría ser confusa, como podemos ver en esta pregunta. Es por eso que prefiero propagar explícitamente los resultados de resolución como lo hizo:

var MyController = function($scope, service) { service.getStuff().then(function(result) { $scope.myVar = result; }); }


@ pkozlowski.opensource respondió el por qué (¡GRACIAS!), pero no cómo evitarlo en las pruebas.

La solución a la que acabo de llegar es probar que HTTP recibe una llamada en el servicio, y luego espiar los métodos de servicio en las pruebas del controlador y devolver los valores reales en lugar de las promesas.

Supongamos que tenemos un servicio de usuario que habla con nuestro servidor:

var services = angular.module(''app.services'', []); services.factory(''User'', function ($q, $http) { function GET(path) { var defer = $q.defer(); $http.get(path).success(function (data) { defer.resolve(data); } return defer.promise; } return { get: function (handle) { return GET(''/api/'' + handle); // RETURNS A PROMISE }, // ... }; });

Al probar ese servicio, no nos importa qué ocurre con los valores devueltos, solo que las llamadas HTTP se realizaron correctamente.

describe ''User service'', -> User = undefined $httpBackend = undefined beforeEach module ''app.services'' beforeEach inject ($injector) -> User = $injector.get ''User'' $httpBackend = $injector.get ''$httpBackend'' afterEach -> $httpBackend.verifyNoOutstandingExpectation() $httpBackend.verifyNoOutstandingRequest() it ''should get a user'', -> $httpBackend.expectGET(''/api/alice'').respond { handle: ''alice'' } User.get ''alice'' $httpBackend.flush()

Ahora en nuestras pruebas de controlador, no hay necesidad de preocuparse por HTTP. Solo queremos ver que el servicio de usuario se está poniendo a trabajar.

angular.module(''app.controllers'') .controller(''UserCtrl'', function ($scope, $routeParams, User) { $scope.user = User.get($routeParams.handle); });

Para probar esto, espiamos el servicio de usuario.

describe ''UserCtrl'', () -> User = undefined scope = undefined user = { handle: ''charlie'', name: ''Charlie'', email: ''[email protected]'' } beforeEach module ''app.controllers'' beforeEach inject ($injector) -> # Spy on the user service User = $injector.get ''User'' spyOn(User, ''get'').andCallFake -> user # Other service dependencies $controller = $injector.get ''$controller'' $routeParams = $injector.get ''$routeParams'' $rootScope = $injector.get ''$rootScope'' scope = $rootScope.$new(); # Set up the controller $routeParams.handle = user.handle UserCtrl = $controller ''UserCtrl'', $scope: scope it ''should get the user by :handle'', -> expect(User.get).toHaveBeenCalledWith ''charlie'' expect(scope.user.handle).toBe ''charlie'';

No es necesario resolver las promesas. Espero que esto ayude.


Tuve un problema similar y dejé mi controlador asignando $ scope.myVar directamente a la promesa. Luego, en la prueba, encadené otra promesa que afirma el valor esperado de la promesa cuando se resuelva. Usé un método de ayuda como este:

var expectPromisedValue = function(promise, expectedValue) { promise.then(function(resolvedValue) { expect(resolvedValue).toEqual(expectedValue); }); }

Tenga en cuenta que dependiendo del orden de cuándo llame a expectPromisedValue y cuando la promesa se resuelva con su código bajo prueba, es posible que deba activar manualmente un ciclo de resumen final para ejecutar para que estos métodos then () se disparen, sin él su la prueba puede pasar independientemente de si el valor resolvedValue es igual al valor expectedValue o no.

Para estar seguro, presione el gatillo en una llamada afterEach () para que no tenga que recordarla para cada prueba:

afterEach(inject(function($rootScope) { $rootScope.$apply(); }));