Sono appena “caduto” su codice JS scritto in questo modo:
angular.element(document).ready(function () { // Fatto così è un casino da testare!! angular.module("ModuloPerCostanti", []) .constant("parametri", { costanteUno: $("#parameters").data("costante-uno"), costanteDur: $("#parameters").data("costante-due") }); angular.module("ModuloPrincipale", ["ModuloPerCostanti", "ModuloPerServizi", "ModuloPerDirettive", "ngSanitize", "ngResource"]) .filter("filtroUno", function () { return function (data) { //DoSomething } }) .filter("formatPrezzo", function () { return function (data) { //DoSomething }; }) .controller("ControllerPrincipale", ["$rootScope", "$scope", "$http", function ($rootScope, $scope, $http) { // DoSomething }]) .controller("ControllerSecondario", ["$rootScope", "$scope", "$compile", "$http", "$window", "$timeout", "$filter", "$sce", function ($rootScope, $scope, $compile, $http, $window, $timeout, $filter, $sce) { //DoSomething }); });
Scritto in questo modo, non sarà testabile a meno di scatenare l’evento “documentReady” e far inizializzare i moduli, i controller, i filtri, le direttive etc… (cfr: http://bittersweetryan.github.io/jasmine-presentation/#slide-17)
Questo perché? Jasmine (framework JavaScript utilizzato per il testing) carica i moduli angular in questo modo:
angular(“ModuloPerCostanti”) // Cercherà ma non troverà il modulo ModuloPerCostanti
angular(“ModuloPrincipale”) // Cercherà ma non troverà il modulo ModuloPrincipale
Purtroppo Jasmine non troverà alcun modulo, perché quella parte di codice, che definisce i moduli, viene avviata solo durante l’evento document.ready.
Il codice è scritto in quel modo perché dipende dell’HTML della pagina, in particolare la parte di “constant” richiede che il documento sia completamente caricato (di fatto quelle non sono costanti… se fosse C# non ci permetterebbe mai di compilare come costante qualche cosa di non noto a compile time); tralasciando tutto questo, come renderlo testabile senza stravolgerlo? (tenete conto che quando bisogna “rendere testabile” un codice qualcosa non funziona; test a parte, significa che stiamo usando un approccio poco lineare difficile da capire, manutenere e non è detto funzioni sempre).
Basta un piccolissimo accorgimento,utilizzando la notazione dei NameSpace javascript, in modo da minimizzare possibli conflitti:
- Dichiariamo il nostro nameSpace
- Creiamo una funzione Init che contenga tutto il nostro codice, con la notazione dei NameSpace:
var NostroNameSpace; // Dichiariamo ma non inizializziamo il namespace (function(tempNameSpace){ // Funzione anonima autochiamante tempNameSpace.Init = function(){ // al parametro tempNameSpace viene agganciata la funzione Init angular.module("ModuloPerCostanti", []) .constant("parametri", { costanteUno: $("#parameters").data("costante-uno"), costanteDur: $("#parameters").data("costante-due") }); angular.module("ModuloPrincipale", ["ModuloPerCostanti", "ModuloPerServizi", "ModuloPerDirettive", "ngSanitize", "ngResource"]) .filter("filtroUno", function () { return function (data) { //DoSomething } }) .filter("formatPrezzo", function () { return function (data) { //DoSomething }; }) .controller("ControllerPrincipale", ["$rootScope", "$scope", "$http", function ($rootScope, $scope, $http) { // DoSomething }]) .controller("ControllerSecondario", ["$rootScope", "$scope", "$compile", "$http", "$window", "$timeout", "$filter", "$sce", function ($rootScope, $scope, $compile, $http, $window, $timeout, $filter, $sce) { //DoSomething }); }; })(NostroNameSpace || (NostroNameSpace = {})); // Se NostroNameSpace non è già stato istanziato, viene qui inizializzato ad un oggetto vuoto, altrimenti sarebbe <em>undefined</em>
- Richiamiamo l’unica funzione NostroNameSpace.Init così: angular.element(document).ready(NostroNameSpace.Init); // Di nuovo usiamo il document ready
Cosa cambia?
In Jasmine, a questo punto, faremo così:
NostroNameSpace.Init();
angular(“ModuloPerCostanti”) // Cercherà e caricherà il modulo ModuloPerCostanti
angular(“ModuloPrincipale”) // Cercherà e caricherà il modulo ModuloPrincipale
Il codice è stato rifattorizzato in maniera molto semplice, ma in questo modo possiamo testare tutto (circa 5/10 minuti di refactor, più che altro per stare attenti a non introdurre errori).