2015-08-23 8 views
6

La mia comprensione è che quando si carica il modulo nei test di unità angolari, viene chiamato il blocco run.Come deve essere gestito il blocco di corsa nei test di unità angolari?

penserei che se si sta testando un componente, non si vorrebbe essere contemporaneamente testare il blocco run perché unità test si suppone per testare un solo unità. È vero?

In tal caso, esiste un modo per impedire l'esecuzione del blocco run? La mia ricerca mi porta a pensare che la risposta sia "no" e che il blocco run venga sempre eseguito quando il modulo è caricato, ma forse c'è un modo per sovrascriverlo. In caso contrario, come potrei testare il blocco run?

blocco Run:

function run(Auth, $cookies, $rootScope) { 
    $rootScope.user = {}; 
    Auth.getCurrentUser(); 
} 

Auth.getCurrentUser:

getCurrentUser: function() { 
    // user is logged in 
    if (Object.keys($rootScope.user).length > 0) { 
    return $q.when($rootScope.user); 
    } 
    // user is logged in, but page has been refreshed and $rootScope.user is lost 
    if ($cookies.get('userId')) { 
    return $http.get('/current-user') 
     .then(function(response) { 
     angular.copy(response.data, $rootScope.user); 
     return $rootScope.user; 
     }) 
    ; 
    } 
    // user isn't logged in 
    else { 
    return $q.when({}); 
    } 
} 

auth.factory.spec.js

describe('Auth Factory', function() { 
    var Auth, $httpBackend, $rootScope, $cookies, $q; 
    var user = { 
    username: 'a', 
    password: 'password', 
    }; 
    var response = { 
    _id: 1, 
    local: { 
     username: 'a', 
     role: 'user' 
    } 
    }; 

    function isPromise(el) { 
    return !!el.$$state; 
    } 

    beforeEach(module('mean-starter', 'ngCookies', 'templates')); 
    beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) { 
    Auth = _Auth_; 
    $httpBackend = _$httpBackend_; 
    $rootScope = _$rootScope_; 
    $cookies = _$cookies_; 
    $q = _$q_; 
    })); 
    afterEach(function() { 
    $httpBackend.verifyNoOutstandingExpectation(); 
    $httpBackend.verifyNoOutstandingRequest(); 
    }); 

    it('#signup', function() { 
    $rootScope.user = {}; 
    $httpBackend.expectPOST('/users', user).respond(response); 
    spyOn(angular, 'copy').and.callThrough(); 
    spyOn($cookies, 'put').and.callThrough(); 
    var retVal = Auth.signup(user); 
    $httpBackend.flush(); 
    expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user); 
    expect($cookies.put).toHaveBeenCalledWith('userId', 1); 
    expect(isPromise(retVal)).toBe(true); 
    }); 

    it('#login', function() { 
    $rootScope.user = {}; 
    $httpBackend.expectPOST('/login', user).respond(response); 
    spyOn(angular, 'copy').and.callThrough(); 
    spyOn($cookies, 'put').and.callThrough(); 
    var retVal = Auth.login(user); 
    $httpBackend.flush(); 
    expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user); 
    expect($cookies.put).toHaveBeenCalledWith('userId', 1); 
    expect(isPromise(retVal)).toBe(true); 
    }); 

    it('#logout', function() { 
    $httpBackend.expectGET('/logout').respond(); 
    spyOn(angular, 'copy').and.callThrough(); 
    spyOn($cookies, 'remove'); 
    Auth.logout(); 
    $httpBackend.flush(); 
    expect(angular.copy).toHaveBeenCalledWith({}, $rootScope.user); 
    expect($cookies.remove).toHaveBeenCalledWith('userId'); 
    }); 

    describe('#getCurrentUser', function() { 
    it('User is logged in', function() { 
     $rootScope.user = response; 
     spyOn($q, 'when').and.callThrough(); 
     var retVal = Auth.getCurrentUser(); 
     expect($q.when).toHaveBeenCalledWith($rootScope.user); 
     expect(isPromise(retVal)).toBe(true); 
    }); 
    it('User is logged in but page has been refreshed', function() { 
     $cookies.put('userId', 1); 
     $httpBackend.expectGET('/current-user').respond(response); 
     spyOn(angular, 'copy').and.callThrough(); 
     var retVal = Auth.getCurrentUser(); 
     $httpBackend.flush(); 
     expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user); 
     expect(isPromise(retVal)).toBe(true); 
    }); 
    it("User isn't logged in", function() { 
     $rootScope.user = {}; 
     $cookies.remove('userId'); 
     spyOn($q, 'when').and.callThrough(); 
     var retVal = Auth.getCurrentUser(); 
     expect($q.when).toHaveBeenCalledWith({}); 
     expect(isPromise(retVal)).toBe(true); 
    }); 
    }); 
}); 

Tentativo 1:

beforeEach(module('mean-starter', 'ngCookies', 'templates')); 
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) { 
    Auth = _Auth_; 
    $httpBackend = _$httpBackend_; 
    $rootScope = _$rootScope_; 
    $cookies = _$cookies_; 
    $q = _$q_; 
})); 
beforeEach(function() { 
    spyOn(Auth, 'getCurrentUser'); 
}); 
afterEach(function() { 
    expect(Auth.getCurrentUser).toHaveBeenCalled(); 
    $httpBackend.verifyNoOutstandingExpectation(); 
    $httpBackend.verifyNoOutstandingRequest(); 
}); 

Questo non funziona. Il blocco run viene eseguito quando il modulo viene caricato, quindi Auth.getCurrentUser() viene chiamato prima che la spia sia impostata.

Expected spy getCurrentUser to have been called. 

Tentativo 2:

beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) { 
    Auth = _Auth_; 
    $httpBackend = _$httpBackend_; 
    $rootScope = _$rootScope_; 
    $cookies = _$cookies_; 
    $q = _$q_; 
})); 
beforeEach(function() { 
    spyOn(Auth, 'getCurrentUser'); 
}); 
beforeEach(module('mean-starter', 'ngCookies', 'templates')); 
afterEach(function() { 
    expect(Auth.getCurrentUser).toHaveBeenCalled(); 
    $httpBackend.verifyNoOutstandingExpectation(); 
    $httpBackend.verifyNoOutstandingRequest(); 
}); 

Questo non funziona perché Auth non è disponibile per essere iniettato prima che il mio modulo di applicazione viene caricato.

Error: [$injector:unpr] Unknown provider: AuthProvider <- Auth 

Tentativo 3:

Come potete vedere, c'è un problema di pollo uova qui. Ho bisogno di iniettare Auth e impostare la spia prima che il modulo sia caricato, ma non posso perché Auth non è disponibile per essere iniettato prima che il modulo sia caricato.

This post sul blog menziona il problema dell'uovo di gallina e offre un'interessante soluzione potenziale. L'autore propone di creare manualmente il mio servizio Auth utilizzando $provideprima del. Carico il mio modulo. Dato che sto creando il servizio, senza iniettarlo, potrei farlo prima che il modulo venga caricato, e potrei installare la spia. Quindi, quando il modulo viene caricato, usa questo servizio di simulazione creato.

ecco il suo codice di esempio:

describe('example', function() { 
    var loggingService; 
    beforeEach(function() { 
     module('example', function ($provide) { 
      $provide.value('loggingService', { 
       start: jasmine.createSpy() 
      }); 
     }); 
     inject(function (_loggingService_) { 
      loggingService = _loggingService_; 
     }); 
    }); 
    it('should start logging service', function() { 
     expect(loggingService.start).toHaveBeenCalled(); 
    }); 
}); 

Il problema con questo, è che ho bisogno del mio servizio Auth! Vorrei solo usare la simulazione per il blocco run; Ho bisogno del mio vero servizio Auth altrove per poterlo testare.

Immagino di poter creare il servizio Auth effettivo utilizzando $provide, ma ciò sembra sbagliato.


domanda finale - per qualsiasi codice finisco usando per affrontare questo problema run blocco, c'è un modo per me per estrarre fuori in modo da non dover riscrivere per ogni mio file spec? L'unico modo in cui potrei pensare di farlo sarebbe usare una sorta di funzione globale.


auth.factory.js

angular 
    .module('mean-starter') 
    .factory('Auth', Auth) 
; 

function Auth($http, $state, $window, $cookies, $q, $rootScope) { 
    return { 
    signup: function(user) { 
     return $http 
     .post('/users', user) 
     .then(function(response) { 
      angular.copy(response.data, $rootScope.user); 
      $cookies.put('userId', response.data._id); 
      $state.go('home'); 
     }) 
     ; 
    }, 
    login: function(user) { 
     return $http 
     .post('/login', user) 
     .then(function(response) { 
      angular.copy(response.data, $rootScope.user); 
      $cookies.put('userId', response.data._id); 
      $state.go('home'); 
     }) 
     ; 
    }, 
    logout: function() { 
     $http 
     .get('/logout') 
     .then(function() { 
      angular.copy({}, $rootScope.user); 
      $cookies.remove('userId'); 
      $state.go('home'); 
     }) 
     .catch(function() { 
      console.log('Problem logging out.'); 
     }) 
     ; 
    }, 
    getCurrentUser: function() { 
     // user is logged in 
     if (Object.keys($rootScope.user).length > 0) { 
     return $q.when($rootScope.user); 
     } 
     // user is logged in, but page has been refreshed and $rootScope.user is lost 
     if ($cookies.get('userId')) { 
     return $http.get('/current-user') 
      .then(function(response) { 
      angular.copy(response.data, $rootScope.user); 
      return $rootScope.user; 
      }) 
     ; 
     } 
     // user isn't logged in 
     else { 
     return $q.when({}); 
     } 
    } 
    }; 
} 

Edit - fallito tentativo + tentativo riuscito:

beforeEach(module('auth')); 
beforeEach(inject(function(_Auth_) { 
    Auth = _Auth_; 
    spyOn(Auth, 'requestCurrentUser'); 
})); 
beforeEach(module('mean-starter', 'ngCookies', 'templates')); 
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) { 
    // Auth = _Auth_; 
    $httpBackend = _$httpBackend_; 
    $rootScope = _$rootScope_; 
    $cookies = _$cookies_; 
    $q = _$q_; 
})); 
// beforeEach(function() { 
// spyOn(Auth, 'getCurrentUser'); 
// }); 
afterEach(function() { 
    expect(Auth.getCurrentUser).toHaveBeenCalled(); 
    $httpBackend.verifyNoOutstandingExpectation(); 
    $httpBackend.verifyNoOutstandingRequest(); 
}); 

Io non sono sicuro perché questo non avrebbe funzionato (indipendente dal problema con l'utilizzo di inject due volte).

Stavo cercando di evitare di dover usare $provide come inizialmente mi sentivo come un hacker/strano per me. Dopo averci pensato un po 'di più però, ora sento che $provide va bene, e che seguire il tuo suggerimento di usare mock-auth è fantastico !!! Entrambi hanno funzionato per me.

In auth.factory.spec.js Ho appena caricato il modulo auth (sto chiamando auth, non mean-auth) senza caricare mean-starter. Questo non ha il problema del blocco run perché quel modulo non ha il codice di blocco run, ma mi consente di testare il mio factory Auth. Altrove, questo funziona:

beforeEach(module('mean-starter', 'templates', function($provide) { 
    $provide.value('Auth', { 
    requestCurrentUser: jasmine.createSpy() 
    }); 
})); 

così come il fantastico mock-auth soluzione:

auth.factory.mock.js

angular 
    .module('mock-auth', []) 
    .factory('Auth', Auth) 
; 

function Auth() { 
    return { 
    requestCurrentUser: jasmine.createSpy() 
    }; 
} 

user.service.spec.js

beforeEach(module('mean-starter', 'mock-auth', 'templates')); 
+2

off-topic alla tua domanda, ma dovresti sapere che '.run' non aspetta' $ http' per il completamento. Se qualcosa nell'app si basa sul risultato che c'è, hai una condizione di gara. In genere, si usa 'resolve' se si utilizza' ngRoute' o 'ui.router'. –

risposta

6

My understanding is that when you load your module in Angular unit tests, the run block gets called.

Corretto.

I'd think that if you're testing a component, you wouldn't want to simultaneously be testing the run block, because unit tests are supposed to just test one unit. Is that true?

anche corretto, nel senso che in questo momento si sta effettivamente verificando l'integrazione di Auth e il vostro blocco di corsa, e non c'è l'isolamento di uno dall'altro.

If so, is there a way to prevent the run block from running? My research leads me to think that the answer is "no", and that the run block always runs when the module is loaded, but perhaps there's a way to override this. If not, how would I test the run block?

Come implementato, no non è possibile impedire l'esecuzione del blocco di esecuzione. Tuttavia, rimane possibile con alcuni piccoli refactoring in quanto la tua domanda è in definitiva quella della modularizzazione. Senza essere in grado di vedere la vostra dichiarazione del modulo, mi immagino che sembra qualcosa di simile:

angular.module('mean-starter', ['ngCookies']) 

    .factory('Auth', function($cookies) { 
    ... 
    }); 

    .run(function(Auth, $rootScope) { 
    ... 
    }); 

Questo modello può essere suddiviso in moduli per sostenere testabilità (e il modulo riusabilità):

angular.module('mean-auth', ['ngCookies']) 

    .factory('Auth', function() { 
    ... 
    }); 

angular.module('mean-starter', ['mean-auth']) 

    .run(function(Auth, $rootScope) { 
    ... 
    }); 

Questo ora consente di testare la fabbrica Auth in isolamento caricando il modulo mean-auth solo nel test.

Mentre questo risolve il problema del blocco di esecuzione che interferisce con i test dell'unità per Auth, si deve ancora affrontare il problema della simulazione di Auth.getCurrentUser in modo da testare il blocco di esecuzione in isolamento. Il post del blog a cui fai riferimento è corretto in quanto dovresti cercare di sfruttare lo stadio di configurazione del modulo per stub/spy sulle dipendenze utilizzate durante la fase di esecuzione. Pertanto, nel test:

module('mean-starter', function ($provide) { 
    $provide.value('Auth', { 
    getCurrentUser: jasmine.createSpy() 
    }); 
}); 

Quanto alla tua ultima domanda, è possibile creare schernisce riutilizzabili dichiarandoli come moduli. Ad esempio, se si voleva creare una fabbrica finto riutilizzabile per Auth si definisce in un file separato caricato prima il test di unità:

angular.module('mock-auth', []) 

.factory('Auth', function() { 
    return { 
    getCurrentUser: jasmine.createSpy() 
    }; 
}); 

e poi caricarlo nel test successivi a qualsiasi modulo in cui si richiede esso, come angolare sovrascriverà qualsiasi servizio con lo stesso nome:

+0

Grazie! Alcune domande (le indago anch'io, ma ho anche pensato che ti avrei chiesto): 1) Ri: deridendo 'Auth.getCurrentUser', è necessario usare' $ provide'? Non potrei semplicemente usare 'spyOn (Auth, 'getCurrentUser')' dopo aver caricato 'mean-auth' e iniettato' Auth'? 2) Attualmente, il mio factory 'Auth' sta usando' $ rootScope' per tenere traccia dell'utente attualmente loggato. Usando un modulo 'mock-auth', il suo' $ rootScope' non sarebbe lo stesso di 'mean-starter''s' $ rootScope' giusto? Ho pensato che fosse un buon caso per '$ rootScope', ma sto pensando a come potrei rifattenerlo ora. –

+0

Ho modificato la mia domanda per includere il mio attuale 'auth.factory.js'. Dopo averci pensato un po 'di più, non sono sicuro di come funzionerebbe il modulo 'mean-auth' separato. Per i primi 3 metodi, potrei solo restituire la promessa dopo '.post' /' .get' e fare il resto della logica nel controller. Ma per 'getCurrentUser', mi piacerebbe fare condizionalmente la richiesta HTTP a seconda se l'utente corrente è già disponibile o meno. Ma come sarebbe "mean-auth" conoscere l'utente corrente? Immagino di poter fare questo controllo altrove? –

+1

1. Non è necessario '$ fornire' per testare 'mean-auth' poiché il suo stato non è più influenzato dal blocco di esecuzione. È necessario utilizzarlo per testare il 'mean-starter', tuttavia, poiché il blocco di esecuzione si attiva immediatamente. 2. Il '$ rootScope' sarà lo stesso. Il modulo 'mock 'di Angular raccoglie i moduli definiti prima di creare l'iniettore, quindi il $ rootScope esiste come ambito genitore (cioè livello superiore) per l''" applicazione ". – scarlz

Problemi correlati