Testing de directivas en AngularJS con Jasmine

0
6845

En este tutorial vas a aprender cómo testear con Jasmine tus directivas personalizadas que puedes crear en AngularJS.

0. Índice de contenidos

1. Introducción

Ya sabes que AngularJS te permite crear tus propias directivas. En Autentia creemos en el TDD y es por ello que antes de escribir el código funcional, escribimos su correspondiente test unitario.

Utilizando el framework Jasmine puedes escribir tests unitarios para AngularJS. En este tutorial vas a aprender cómo hacer tests para tus directivas con dos ejemplos, uno para una directiva elemento HTML y otro para una directiva de validación personalizada de controles de formulario.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10.3
  • Plunker – AngularJS 1.4.3 + Jasmine 2.2.1

Para añadir a tu entorno la configuración de Jasmine y Karma puedes visitar el tutorial en el que Miguel Arlandy nos explica los tests unitarios en AngularJS para filtros, servicios y controladores.

3. Testing de directiva de elemento HTML

Vamos a realizar una directiva tnt-warning para utilizar por ejemplo cuando hay que avisar al usuario de que revise una sección de un formulario en la que se encuentre.

La directiva será utilizada como etiqueta, por lo que cuando en la vista se escriba , esta será sustituida por la plantilla que se indica en la directiva, en este caso

<p>Revisa que el apartado {{section}} es correcto</p>

tnt-warning-test.js

describe('Test unitario de elemento HTML', function() {
  var $compile,
      $rootScope;


  beforeEach(module('adictosTestingDirectivaHTML'));


  // Se almacenan las referencias a $rootScope y $compile
  // para que estén disponibles en todos los tests de este bloque
  beforeEach(inject(function(_$compile_, _$rootScope_){
    $compile = _$compile_;
    $rootScope = _$rootScope_;


    // Sección en la que se encuentra el usuario
    $rootScope.section = 3;
  }));


  it('debería sustituir el elemento tnt-warning por el contenido indicado en el template', function() {


    // Compila código HTML que contiene la directiva
    var element = $compile("<tnt-warning></tnt-warning>")($rootScope);


    // Ejecuta los wathches registrados en el $scope, de esta manera
    // los valores del modelo se propagan al DOM
    $rootScope.$digest();


    // Comprueba si el elemento compilado contiene el contenido de la
    // plantilla
    expect(element.html()).toContain("Revisa que el apartado 3 es correcto");


  });
});


La directiva tnt-warning es la siguiente:

tnt-warning.js

var app = angular.module('adictosTestingDirectivaHTML', []);


app.directive('tntWarning', function () {
    return {
        restrict: 'E',
        replace: true,
        template: '<p>Revisa que el apartado {{section}} es correcto</p>'
    };
});

El código fuente de este test se encuentra en plunker.

4. Testing de directiva de validación personalizada de formularios

4.1. Testing validación síncrona

En un tutorial anterior os explicaba cómo validar campos de formularios de manera personalizada en AngularJS utilizando directivas. Para este ejemplo vamos a hacer un test de la directiva tnt-nif-validation (si seguimos TDD, recuerda que primero hay que hacer el test y después la directiva).

La directiva tnt-nif-validation comprueba si el número de un DNI es correcto. El test que hay que preparar es el siguiente:

tnt-nif-validation-test.js

describe("testsDirectivaValidacionFormularioSincrona", function() {


var $compile,
    $rootScope;


  beforeEach( function(){
    module('formAdictos')
  });


  beforeEach(inject(function(_$compile_, _$rootScope_){
    $compile = _$compile_;
    $rootScope = _$rootScope_;
    
    var element = angular.element(
      '<form name="userForm"><input name="nif" type="text" ng-model="user.nif" tnt-nif-validation /></form>');
    
    $compile(element)($rootScope);
    $rootScope.$digest();
    userForm = $rootScope.userForm;
  }));


  it('debería de ser válido al inicio', function() {
    expect(userForm.nif.$valid).toBe(true);
  });
  
  it('debería de ser inválido con un valor de DNI inventado', function() {
    userForm.nif.$setViewValue('12345678K');
    expect(userForm.nif.$invalid).toBe(true);
  });


});


Los tests se han diseñado en base a los estados que puede tomar el controlador del modelo del control nif. El primer test comprueba que el estado inicial es válido. En el segundo, se comprueba que ante un número de DNI que no es correcto, la directiva detecta que el valor es inválido.

El código de la directiva es el siguiente:

tnt-nif-validation.js

var app = angular.module('formAdictos', []);


app.directive('tntNifValidation', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$validators.tntnifvalidation = function(modelValue, viewValue) {


        if (ctrl.$isEmpty(modelValue)) {
          // tratamos los modelos vacíos como correctos
          return true;
        }


        if (viewValue) {


          var letterValue = viewValue.substr(viewValue.length - 1);
          var numberValue = viewValue.substr(viewValue.length - (viewValue.length - 1));
          var controlLetter = "TRWAGMYFPDXBNJZSQVHLCKE".charAt(numberValue % 23);


            if(letterValue === controlLetter ){
              return true;
            } else {
              return false;
            }
        }


      // NIF inválido
      return false;


      };
    }
  };
});


Como puedes ver, la validación es síncrona. En el tutorial de creación de directivas de validación explicaba que también es posible hacer validaciones asíncronas, como por ejemplo, cuando queremos validar que un usuario está registrado en el sistema.

El código fuente de este test se encuentra en plunker.

4.2. Testing validación asíncrona

Para explicar la validación asíncrona se realizó la directiva tnt-user-signedup. Esta directiva simulaba con una promesa la llamada http al servicio. Para esta ocasión y poder mostrar los mocks de llamadas http para hacer tests se ha implementado la llamada al servicio.

Por tanto, lo que queremos es realizar una llamada a un servicio back-end que nos indique si un alias de usuario está dado de alta en el sistema o no. El test a realizar es el siguiente:

tnt-user-signedup-test.js

describe("testsDirectivaValidacionFormularioAsincrona", function() {


  var $compile,
      $rootScope,
      $httpBackend;


  beforeEach( function(){
    module('formAdictos')
  });


  beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_){
    
    $compile = _$compile_;
    $rootScope = _$rootScope_;
    $httpBackend = _$httpBackend_;
    
    $httpBackend.when('GET', 'api/users/loginManagement?alias=juan')
        .respond(400, {mensaje: 'usuario no registrado'});
        
    $httpBackend.when('GET', 'api/users/loginManagement?alias=adrian')
        .respond(200, {mensaje: 'usuario registrado'});
        
    $rootScope.user = {alias: null};
    
    var element = angular.element(
        '<form name="userForm"><input name="alias" type="text" ng-model="user.alias" tnt-user-signedup /></form>'
    );
    
    $compile(element)($rootScope);
    userForm = $rootScope.userForm;
    
  }));


  it('debería detectar que el alias no está dado de alta', function () {
        userForm.alias.$setViewValue('juan');
        $rootScope.$digest();
        $httpBackend.flush();
        expect(userForm.alias.$error.tntusersignedup).toBeTruthy(); // lo del error
  });
  
  it('debería detectar que el alias está dado de alta', function () {
        userForm.alias.$setViewValue('adrian');
        $rootScope.$digest();
        $httpBackend.flush();
        expect(userForm.alias.$valid).toBeTruthy();
  });
  


});


El módulo que nos permite realizar mocks en los tests de AngularJS es ng-mock, el cual hay que importar en el módulo de la aplicación. El módulo ng-mock nos provee el servicio $httpbackend para simular las llamadas a back-end. Las llamadas a servicios mockeadas pueden responder de manera síncrona, por lo que es necesario llamar a la función flush() para que se liberen las peticiones pendientes.

El código de la directiva tnt-user-signedup es el siguiente

tnt-user-signedup.js

var app = angular.module('formAdictos', ['ngMock']);


app.directive('tntUserSignedup', ['$http', function ($http) {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
   
    var urlAPI = 'api/users/loginManagement';


    ctrl.$asyncValidators.tntusersignedup = function(modelValue, viewValue) {
      return $http({method: 'GET', url: urlAPI, params: {alias: viewValue}});
    };
   
    }
  };
}]);


Como puedes ver, se ha inyectado el servicio $http del módulo ng para poder realizar las llamadas al back-end. Al crear el módulo se le indica que se inyecte el módulo ng-mock.

El código fuente de este test se encuentra en plunker.

5. Conclusiones

Implementando tests para nuestras directivas estaremos más seguros durante el desarrollo de nuestra aplicación ya que nos avisarán cuando el comportamiento de la aplicación haya sido modificado de una manera que estos no contemplan.

La combinación de Jasmine, que nos ofrece una manera limpia de realizar tests unitarios, y de los servicios para mockear implementados en Angular en la directiva ng-mock, conforman una herramienta muy potente que nos ayuda y simplifica seguir la filosofía TDD.

6. Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad