E2E se burla de $ httpBackend en realidad no pasa a través de mí

Aunque creo que estoy siguiendo las instrucciones aquí para configurar $ httpBackend para pasar solicitudes seleccionadas al servidor, no funciona para mí.

Aquí hay un Plunkr con una prueba fallida que muestra lo que estoy haciendo y explica en los comentarios lo que parece estar yendo mal.

Mi espeleología sugiere que, por alguna razón, el $httpBackend no tiene una copia interna del $httpBackend real, $httpBackend lo que, cuando llega el momento de pasar a través de la solicitud XHR, lo pasa al $httpBackend en $httpBackend lugar. Esa segunda llamada arroja una excepción porque no sabe qué hacer con la solicitud.

Respuesta a dtabuenc

Recuerdo con aprecio tu publicación en las pruebas de mitad de camino. Identifica una gama importante de pruebas de integración que se encuentran entre las pruebas de unidad y E2E. Estoy parado en ese terreno intermedio.

No creo que estés siendo sarcástico para nada. Su respuesta es perfectamente razonable … o sería razonable si no fuera contradicha por el texto de ” API reference / ngMockE2E / $ httpBackend “. Yo cito:

Esta implementación se puede utilizar para responder con respuestas estáticas o dinámicas a través de la API y sus accesos directos (cuando whenGET , whenPOST , etc.) y opcionalmente pasar solicitudes al $httpBackend real para solicitudes específicas (por ejemplo, para interactuar con ciertas API remotas o para buscar plantillas de un servidor web)

[I] n un escenario de prueba de extremo a extremo o en un escenario cuando una aplicación se está desarrollando con la API real sustituida por una simulación , a menudo es deseable para determinada categoría de solicitudes eludir la simulación y emitir un http real request …. Para configurar el backend con este comportamiento use el manejador de solicitud passThrough de when en lugar de respond . [énfasis mío].

La documentación no $httpBackend uso de E2E $httpBackend en un entorno Jasmine. No puedo pensar en una razón para excluirlo. Si existe tal razón, deberían indicarlo claramente. En serio, ¿quién lee sobre un componente simulado y no prevé utilizarlo en un entorno de prueba?

Pasar solicitudes al $httpBackend real para solicitudes específicas, por ejemplo, para interactuar con ciertas aplicaciones remotas ” es precisamente lo que pretendo hacer. ¿Qué podrían querer decir con “real $ httpBackend” excepto la versión no simulada de ese componente?

No entiendo tu afirmación de que

El módulo ngMocksE2E está diseñado para ser utilizado en el lado “servidor” de las cosas donde se está ejecutando la aplicación angular real.

La palabra “servidor” aparece exactamente 3 veces en esa página, ni una sola vez sugiere que ningún código de aplicación se ejecute en un “servidor”. No sé a qué te refieres con la “aplicación angular real” que se ejecuta en el lado “servidor” de las cosas “.

La documentación es perfectamente clara que el E2E $httpBackend no está limitado a las pruebas E2E. También es para ” un escenario en el que se está desarrollando una aplicación con la API real sustituida por una simulación “.

Eso está a solo un paso de mi escenario en el que una aplicación está siendo probada con la API real “.

En mis escenarios, el SUT está recurriendo a un componente que obtiene datos de un servidor. Mis pruebas existen para verificar que este componente dependiente logre tales solicitudes del back-end real y recuperará o guardará los datos de la manera esperada. Esta es una prueba de integración que no se puede satisfacer adecuadamente burlando el comportamiento del back-end.

Por supuesto, puedo probar (y hacer pruebas) con respuestas simuladas de XHR la capacidad del componente para responder adecuadamente a lo que predigo será el comportamiento del progtwig de fondo. Eso no es lo mismo que validar que el componente responde apropiadamente al comportamiento real del backend … lo que podría cambiar a medida que la aplicación evoluciona y se aparta de las respuestas falsas de alguna manera significativa.

Consideraría usar tu probador de mitad de camino para este propósito si entendiera cómo cambiarlo en la ruta del código del SUT. Yo no. Creo que el componente que hace las solicitudes de XHR es inaccesible para su ngMidwayTester . Pero sí sé cómo meter un verdadero XHR helper en la tubería si es necesario.

Aquí es donde estoy parado en este momento .

O alguien puede mostrar cómo hacer que $httpBackend pase ciertas solicitudes al servidor, como la documentación proclama que puede hacerlo, o reemplazaré la implementación de passThrough por una implementación XHR funcional.

Prefiero la primera opción. Si me dirijo al segundo, ofreceré un enlace aquí para el beneficio de otros que comparten mis necesidades y mi interpretación de la documentación de API.

¿Hay una tercera forma en la que me estoy perdiendo?

La siguiente es una explicación del propósito del $httpBackend que está en el módulo ngMockE2E .

El módulo ngMockE2E simplemente no está diseñado ni pretende ser utilizado desde una especificación de jasmine.

Cuando se realizan pruebas de extremo a extremo, la prueba tiene dos caras. Una es la aplicación angular que se está probando, la otra es el código de escenario angular que vive en la especificación de jasmine.

Bajo las pruebas E2E no hay módulos angulares, ng-mocks ni nada relacionado con el ángulo en el lado del jasmine (aparte del corredor de escenario).

El módulo ngMocksE2E está diseñado para ser utilizado en el lado “servidor” de las cosas donde se está ejecutando la aplicación angular real. Su principal objective es permitirnos responder de manera predeterminada para que las pruebas de interfaz de usuario de nivel de integración puedan avanzar mucho más rápido que si cada página fuera al servidor para JSON.

Al usar jasmine junto con ng-mocks , angular siempre reemplazará el $ httpBackend con el backend simulado. Al agregar el módulo ngMocksE2E , no podrá obtener ningún $httpBackend “real” y, como ya se ha $httpBackend , simplemente envolverá el simulacro y lo delegará en el paso.

Parecería que el tipo de prueba que intenta escribir es una prueba que no prueba la integración de la interfaz de usuario, pero prueba la aplicación javascript y la integración del servidor.

Este es un estilo de prueba perfectamente legítimo (conocido como “prueba de mitad de camino” en la comunidad angular). Tu problema es que estás usando la herramienta incorrecta.

Yo echaría un vistazo a esto:

https://github.com/yearofmoo/ngMidwayTester

Que usaría en lugar de angular-mocks y angular.module () para facilitar el tipo de prueba que estoy asumiendo que desea hacer.

Puedes leer más sobre esto aquí:

http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html

(disculpas si ya has sido vinculado allí)

EDITAR: (Para abordar comentarios adicionales en cuestión)

Usted tiene una gran ventaja en cuanto a que la documentación no está clara de que ngMockE2E no se pueda utilizar en el lado del cliente (es decir, karma / jasmine) de una configuración de prueba de extremo a extremo. No es irrazonable interpretar cosas como las ha interpretado, pero no cambia el hecho de que la interpretación es incorrecta.

El ngMockE2E pasará las solicitudes si se lo indica cuando se utiliza en el lado del servidor de una aplicación en lugar de en el lado del cliente. Esto significa que todavía puede pasar ciertas solicitudes que son difíciles de burlar como respuestas pre-enlatadas. Lo que quiero decir por cliente y servidor es que en las pruebas de extremo a extremo hay dos extremos. Tiene la aplicación que se probará que es servida por un servidor de aplicaciones estándar, y tiene el código de prueba que está impulsando la aplicación que generalmente se ejecuta en Karma u otro corredor de prueba, que usa solicitudes HTTP estándar para comunicarse con la aplicación que se está ejecutando en otro proceso

Si observa la documentación y cómo configurar ngMockE2E , notará que no se menciona a Jasmine, y las instrucciones son para configurarlo en una aplicación angular real:

 myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); myAppDev.run(function($httpBackend) { phones = [{name: 'phone1'}, {name: 'phone2'}]; // returns the current list of phones $httpBackend.whenGET('/phones').respond(phones); // adds a new phone to the phones array $httpBackend.whenPOST('/phones').respond(function(method, url, data) { phones.push(angular.fromJson(data)); }); $httpBackend.whenGET(/^\/templates\//).passThrough(); //... }); 

Como puede ver en este ejemplo, se están burlando de todas las instrucciones de datos JSON, al tiempo que le permiten obtener las plantillas del servidor.

Para usarlo desde jasmine, la configuración sería bastante diferente, usando angular.mock.module('ngMockE2E') y luego configurando $httpBackend.whenGET() en a beforeEach() lugar de en module.run() .

En cuanto a ngMidwayTester lo ngMidwayTester , creo que esto, de hecho, sería compatible con ngMockE2E . Esencialmente ngMidwayTester reemplaza angular.mock.module() e inject() con sus propias implementaciones. Entonces podrías usarlo así:

 beforeEach(function(){ tester = ngMidwayTester('app', 'ngMockE2E'); $http = tester.inject('$http'); $httpBackend = tester.inject('$httpBackend'); $rootScope = tester.inject('$rootScope'); }); 

Esto debería funcionar, porque ya no está utilizando el módulo ngMock (que siempre se incluye cuando usa angular.mock.module() ). Las cosas deberían funcionar exactamente como usted quiere usando ngMidwayTester .

Me encontré con el mismo problema, pero en lugar de implementar una API enriquecedora o reemplazar los buriles angulares originales, simplemente agregué el siguiente ayudante:

 angular.module('httpReal', ['ng']) .config(['$provide', function($provide) { $provide.decorator('$httpBackend', function() { return angular.injector(['ng']).get('$httpBackend'); }); }]) .service('httpReal', ['$rootScope', function($rootScope) { this.submit = function() { $rootScope.$digest(); }; }]); 

Repara dos problemas que impiden que una solicitud HTTP se transmita:

  1. Restaura original $httpBackend ;

  2. Proporciona un método para cumplir con las solicitudes, ya que de lo contrario estarían en la cola de AngularJS esperando un ciclo de resumen.

 describe('my service', function() { var myService, httpReal; beforeEach(module('myModule', 'httpReal')); beforeEach(inject(function( _myService_, _httpReal_ ) { myService = _myService_; httpReal = _httpReal_; })); it('should return valid data', function(done) { myService.remoteCall().then( function(data) { expect(data).toBeDefined(); done(); }, function(error) { expect(false).toBeTruthy(); done(); }); httpReal.submit(); }); }); 

Para probar mi aplicación con llamadas reales al back-end utilicé una versión modificada de angular-mocks

Funciona igual que para pruebas unitarias en Jasmine.

Lo estoy usando con Jasmine 2.0, por lo que una prueba se parece a lo siguiente:

 it(' myTest', function (done) { _myService.apiCall() .then(function () { expect(true).toBeTruthy(); done() }); }); 

NB: el done es necesario debido a la llamada asincrónica.

Aquí hay una solución que uso para hacer llamadas HTTP reales cuando estoy usando ngMock para pruebas unitarias. Lo uso principalmente para depurar, probar la API, obtener ejemplos de JSON, etc.

Escribí una publicación más detallada sobre la solución en mi blog: Cómo probar la unidad con llamadas HTTP reales usando ngMockE2E y passThrough .

La solución es la siguiente:

 angular.mock.http = {}; angular.mock.http.init = function() { angular.module('ngMock', ['ng', 'ngMockE2E']).provider({ $exceptionHandler: angular.mock.$ExceptionHandlerProvider, $log: angular.mock.$LogProvider, $interval: angular.mock.$IntervalProvider, $rootElement: angular.mock.$RootElementProvider }).config(['$provide', function($provide) { $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); $provide.decorator('$$rAF', angular.mock.$RAFDecorator); $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); $provide.decorator('$controller', angular.mock.$ControllerDecorator); }]); }; angular.mock.http.reset = function() { angular.module('ngMock', ['ng']).provider({ $browser: angular.mock.$BrowserProvider, $exceptionHandler: angular.mock.$ExceptionHandlerProvider, $log: angular.mock.$LogProvider, $interval: angular.mock.$IntervalProvider, $httpBackend: angular.mock.$HttpBackendProvider, $rootElement: angular.mock.$RootElementProvider }).config(['$provide', function($provide) { $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); $provide.decorator('$$rAF', angular.mock.$RAFDecorator); $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); $provide.decorator('$controller', angular.mock.$ControllerDecorator); }]); }; 

Incluya este archivo fuente después de ngMock, por ejemplo:

     

Cómo escribir la prueba?

  describe('http tests', function () { beforeEach(module('moviesApp')); var $controller; var $httpBackend; var $scope; describe('real http tests', function() { beforeEach(angular.mock.http.init); afterEach(angular.mock.http.reset); beforeEach(inject(function(_$controller_, _$httpBackend_) { $controller = _$controller_; $scope = {}; $httpBackend = _$httpBackend_; // Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request $httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough(); })); it('should load default movies (with real http request)', function (done) { var moviesController = $controller('MovieController', { $scope: $scope }); setTimeout(function() { expect($scope.movies).not.toEqual([]); done(); }, 1000); }); }); }); 

¿Cómo funciona?

Utiliza la versión ngMockE2E de $ httpBackEndProvider, que nos proporciona la función passThrough que vemos que se usa en la prueba. Esto funciona como lo sugiere el nombre y permite el paso de una llamada HTTP nativa.

Necesitamos redefinir el módulo ngMock sin su versión falsa del $ BrowserProvider, ya que eso es lo que evita las llamadas HTTP reales en las pruebas unitarias que usan ngMock.

¿Por qué lo hago así?

Me gusta la flexibilidad para poder cambiar fácilmente entre usar falsificaciones y llamadas HTTP reales, ya que ayuda a mi flujo de trabajo al escribir las pruebas, por lo que se utiliza la versión ngMockE2E de $ httpBackEndProvider. También me permite codificar las pruebas unitarias de la misma manera que usando ngMock, donde simplemente puedo ingresar / salir de la línea beforeEach / afterEach para registrar angular.mock.http.init / reset.

Plunkr

Aquí hay un Plunkr con un ejemplo .