Unit test de un controlador en ionic framework

Una buena práctica en el desarrollo de software es la utilización de Tests Unitarios.

A veces, la gestión de eventos en nuestro controlador (como escuchar el evento $ionicView.beforeEnter) o el uso de llamadas asíncronas como $ionicPlatform.ready() pueden parecer que dificultan el testeo del mismo, pero en realidad no tienen dificultad.

En este post voy a explicarte como testear la carga de un controlador de ionic. Asumiré que conoces y tienes instalado Jasmine y Karma, que forman parte de los mecanismos habituales para hacer Tests Unitarios en web.

Código de ejemplo

Para evitar distracciones, nos centraremos solo en lo imprescindible, el controlador y el test.

Controlador

El controlador, asigna una variable message cuando se llama al evento $ionicView.beforeEnter que indica que la vista tiene que cargarse (como sería el caso cuando la navegación de nuestra app lleva a la vista que gestiona este controlador), y solo a partir de la detección del evento deviceready de phonegap.

Además, esta variable que asignamos procede de un parámetro helloMessage en la url (que se gestiona con el servicio $stateParams que incluye ionic). Más adelante veremos como éste detalle puede complicar el test.


angular.module('app.hello', [])
.controller('HelloCtrl', ['$scope', '$stateParams', '$ionicPlatform', function($scope, $stateParams, $ionicPlatform) {


var initView = function(){
$scope.message = $stateParams.helloMessage;
}


$scope.$on('$ionicView.beforeEnter', function(){
$ionicPlatform.ready(function() {
initView();
});
});
}]);

Test

Los tests mirarán, en primer lugar, que haya un $scope definido, y posteriormente, que el mensaje que hemos asignado a $scope.message, sea el que contiene el servicio $stateParams.

describe('HelloCtrl tests', function(){
var greetingMessage = 'Hi World!!';
var $scope = {};
var ctrl = {};

beforeEach(module("ionic"));
beforeEach(module("app.hello"));

beforeEach(inject(function ($rootScope, $controller, $stateParams, $ionicPlatform) {
// create a scope object for us to use.
$scope = $rootScope.$new();
//hardcore $stateParams.helloMessage, so the controller receives that argument
$stateParams.helloMessage = greetingMessage;

ctrl = $controller("HelloCtrl", {
$scope: $scope
});
}));


//Make sure that deviceready event has been called, and call $ionicView.beforeEnter event
beforeEach(function(done){
$scope.$broadcast('$ionicView.beforeEnter');
ionic.Platform.ready(function() {
done();
});
});



// Test 1: Simple test,
it("have a $scope variable defined", function() {
expect($scope).toBeDefined();
});

// Test 2: Check that message has been initialized
it("SHOULD have assigned $stateParams.helloMessage", function() {
expect($scope.message).toEqual(greetingMessage);
});


});

DETALLE: en uno de los beforeEach, incluimos el módulo de ionic. Esto es para prevenir el error de inyección de dependencias que nos aparecería, debido a que estamos usando el servicio $stateParams.

Eventos asíncronos

Un detalle interesante de los tests anteriores es como solucionamos la cuestión de los eventos con el método done().

En un beforeEach, llamamos al método done() (que hemos pasado como argumento a la función) dentro del callback de ionic.Platform.ready. Al incluir esta línea, provocamos el bloqueo de la ejecución de tests hasta que se ejecuta la llamada a done(), por lo que cuando continua la ejecución, podemos garantizar que ya se habrán ejecutado otras llamadas que estén dentro de ionic.Platform.ready como es el caso de la inicialización que hacemos en nuestro controlador.

Además, también nos aseguramos de que se haya ejecutado el código que justamente queremos testear al forzar, antes del test, que se propague el evento $ionicView.beforeEnter que está esperando nuestro controlador para inicializar sus variables.

Resultados

Si ejecutamos nuestro test (pongamos que está configurado en tests/mi.conf.js)

$ karma start tests/mi.conf.js

Veremos como efectivamente, hemos pasado los dos. ¡FELICIDADES!