unitarias unit test pruebas hacer como unit-testing angularjs angularjs-directive jasmine karma-runner

unit testing - test - Controladores de directivas de pruebas unitarias en Angular sin hacer que el controlador sea global



pruebas unitarias angularjs (5)

¿Hay algo malo en hacerlo de esta manera? Parece preferible ya que evita colocar el controlador en el espacio de nombre global y puede probar lo que quiere (es decir, el controlador) sin necesidad de $ compilar html.

Ejemplo de definición de directiva:

.directive(''tabs'', function() { return { restrict: ''EA'', transclude: true, scope: {}, controller: function($scope, $attrs) { this.someExposedMethod = function() {}; }, templateUrl: ''template/tabs/tabs.html'', replace: true };

Luego, en su prueba de Jasmine, solicite la directiva que creó usando "nombre + directiva" (por ejemplo, "tabsDirective"):

var tabsDirective = $injector.get(''tabsDirective'')[0]; // instantiate and override locals with mocked test data var tabsDirectiveController = $injector.instantiate(tabsDirective.controller, { $scope: {...} $attrs: {...} });

Ahora puedes probar los métodos del controlador:

expect(typeof tabsDirectiveController.someExposedMethod).toBe(''function'');

En el excelente repositorio de Vojta Jina, donde demuestra las pruebas de directivas, define el controlador de directivas fuera del envoltorio del módulo. Vea aquí: https://github.com/vojtajina/ng-directive-testing/blob/master/js/tabs.js

¿No es esa mala práctica y contaminar el espacio de nombres global?

Si tuviéramos otro lugar donde sería lógico llamar a TabsController, ¿no rompería eso?

Las pruebas para la directiva mencionada se encuentran aquí: https://github.com/vojtajina/ng-directive-testing/commit/test-controller

¿Es posible probar los controladores de directivas por separado del resto de la directiva, sin colocar el controlador en un espacio de nombre global?

Sería bueno encapsular toda la directiva dentro de la definición de la aplicación.directiva (...).



Excelente pregunta!

Por lo tanto, esta es una preocupación común, no solo con los controladores sino también potencialmente con los servicios que una directiva podría necesitar para realizar su trabajo pero que no necesariamente quieren exponer este controlador / servicio al "mundo externo".

Creo firmemente que los datos globales son malvados y deben evitarse, y esto también se aplica a los controladores de directivas . Si tomamos esta suposición podemos tomar varios enfoques diferentes para definir esos controladores "localmente". Al hacerlo, debemos tener en cuenta que un controlador debe ser aún "fácilmente" accesible para las pruebas unitarias, por lo que no podemos simplemente ocultarlo en el cierre de la directiva. Las posibilidades de IMO son:

1) En primer lugar, podríamos simplemente definir el controlador de la directiva en un nivel de módulo , ex ::

angular.module(''ui.bootstrap.tabs'', []) .controller(''TabsController'', [''$scope'', ''$element'', function($scope, $element) { ... }]) .directive(''tabs'', function() { return { restrict: ''EA'', transclude: true, scope: {}, controller: ''TabsController'', templateUrl: ''template/tabs/tabs.html'', replace: true }; })

Esta es una técnica simple que estamos usando en https://github.com/angular-ui/bootstrap/blob/master/src/tabs/tabs.js que se basa en el trabajo de Vojta.

Si bien esta es una técnica muy simple, debe tenerse en cuenta que un controlador todavía está expuesto a toda la aplicación, lo que significa que otro módulo podría anularlo. En este sentido, hace que un controlador sea local para la aplicación AngularJS (para no contaminar el alcance de una ventana global) pero también es global para todos los módulos AngularJS.

2) Use un alcance de cierre y una configuración de archivos especiales para probar .

Si queremos ocultar por completo una función de controlador, podemos ajustar el código en un cierre. Esta es una técnica que AngularJS está usando. Por ejemplo, mirando al NgModelController podemos ver que está definido como una función "global" en sus propios archivos (y por lo tanto de fácil acceso para las pruebas) pero el archivo completo se cierra durante el tiempo de compilación:

En resumen: la opción (2) es "más segura", pero requiere un poco de configuración inicial para la compilación.


Prefiero a veces incluir mi controlador junto con la directiva, así que necesito una forma de probar eso.

Primero la directiva

angular.module(''myApp'', []) .directive(''myDirective'', function() { return { restrict: ''EA'', scope: {}, controller: function ($scope) { $scope.isInitialized = true }, template: ''<div>{{isInitialized}}</div>'' } })

Luego las pruebas:

describe("myDirective", function() { var el, scope, controller; beforeEach inject(function($compile, $rootScope) { # Instantiate directive. # gotacha: Controller and link functions will execute. el = angular.element("<my-directive></my-directive>") $compile(el)($rootScope.$new()) $rootScope.$digest() # Grab controller instance controller = el.controller("myDirective") # Grab scope. Depends on type of scope. # See angular.element documentation. scope = el.isolateScope() || el.scope() }) it("should do something to the scope", function() { expect(scope.isInitialized).toBeDefined() }) })

Consulte la documentación de angular.element para obtener más formas de obtener datos de una directiva instanciada.

Tenga en cuenta que la creación de instancias de la directiva implica que el controlador y todas las funciones de enlace ya se habrán ejecutado, por lo que podría afectar sus pruebas.


Use IIFE, que es una técnica común para evitar el conflicto del espacio de nombres global y también salva la complicada gimnasia en línea, además de brindar libertad en su alcance.

(function(){ angular.module(''app'').directive(''myDirective'', function(){ return { ............. controller : MyDirectiveController, ............. } }); MyDirectiveController.$inject = [''$scope'']; function MyDirectiveController ($scope) { } })();