2013-07-02 17 views
43

Ho una funzione all'interno di uno dei miei servizi angolari che mi piacerebbe essere chiamata più volte a intervalli regolari. Mi piacerebbe farlo usando $ timeout. Sembra qualcosa di simile:

var interval = 1000; // Or something 

var _tick = function() { 
    $timeout(function() { 
     doStuff(); 
     _tick(); 
    }, interval); 
}; 

_tick(); 

stumped su come test di unità questo con Jasmine in questo momento - Come posso fare questo? Se utilizzo $timeout.flush(), le chiamate alle funzioni si verificano indefinitamente. Se utilizzo l'orologio fittizio di Jasmine, $timeout sembra non essere interessato. In sostanza se posso ottenere questo lavoro, dovrei essere a posto:

describe("ANGULAR Manually ticking the Jasmine Mock Clock", function() { 
    var timerCallback, $timeout; 

    beforeEach(inject(function($injector) { 
     $timeout = $injector.get('$timeout'); 
     timerCallback = jasmine.createSpy('timerCallback'); 
     jasmine.Clock.useMock(); 
    })); 

    it("causes a timeout to be called synchronously", function() { 
     $timeout(function() { 
      timerCallback(); 
     }, 100); 
     expect(timerCallback).not.toHaveBeenCalled(); 
     jasmine.Clock.tick(101); 
     expect(timerCallback).toHaveBeenCalled(); 
    }); 
}); 

Queste due varianti funzionano, ma non mi aiuti:

describe("Manually ticking the Jasmine Mock Clock", function() { 
    var timerCallback; 

    beforeEach(function() { 
     timerCallback = jasmine.createSpy('timerCallback'); 
     jasmine.Clock.useMock(); 
    }); 

    it("causes a timeout to be called synchronously", function() { 
     setTimeout(function() { 
      timerCallback(); 
     }, 100); 
     expect(timerCallback).not.toHaveBeenCalled(); 
     jasmine.Clock.tick(101); 
     expect(timerCallback).toHaveBeenCalled(); 
    }); 
}); 

describe("ANGULAR Manually flushing $timeout", function() { 
    var timerCallback, $timeout; 

    beforeEach(inject(function($injector) { 
     $timeout = $injector.get('$timeout'); 
     timerCallback = jasmine.createSpy('timerCallback'); 
    })); 

    it("causes a timeout to be called synchronously", function() { 
     $timeout(function() { 
      timerCallback(); 
     }, 100); 
     expect(timerCallback).not.toHaveBeenCalled(); 
     $timeout.flush(); 
     expect(timerCallback).toHaveBeenCalled(); 
    }); 
}); 

Grazie in anticipo!

+0

Prova iniettando '$ rootScope' e chiamando' $ rootScope. $ Applicare() 'dopo aver spinto l'orologio in avanti. –

risposta

51

Non eseguire il test Asincrono utilizzando l'orologio di Jasmine. Utilizzare invece $timeout.flush() per mantenere in modo sincrono il flusso del test. Potrebbe essere un po 'complicato da configurare, ma una volta ottenuto, i test saranno più veloci e controllati.

Ecco un esempio di un test che lo fa con questo approccio: https://github.com/angular/angular.js/blob/master/test/ngAnimate/animateSpec.js#L618

+0

La tua risposta risolve il problema, oltre a reiterare un'importante regola empirica: i test unitari dovrebbero essere sempre sincroni – eitanfar

44

@ risposta di matsko mi ha portato verso il basso la strada giusta. Ho pensato di pubblicare la mia soluzione "completa" per rendere più semplice la risposta.

La cosa da testare

angular.module("app").service("MyService", function() { 
    return { 
     methodThatHasTimeoutAndReturnsAPromise: function($q, $timeout) { 
      var deferred = $q.defer(); 
      $timeout(function() { 
       deferred.resolve(5); 
      }, 2000); 
      return deferred.promise; 
     } 
    }; 
}); 

Il test

describe("MyService", function() { 
    var target, 
     $timeout; 
    beforeEach(inject(function(_$timeout_, MyService) { 
     $timeout = _$timeout_; 
     target = MyService; 
    })); 
    beforeEach(function(done) { 
     done(); 
    }); 
    it("equals 5", function(done) { 
     target.methodThatHasTimeoutAndReturnsAPromise().then(function(value) { 
      expect(value).toBe(5); 
      done(); 
     }); 
     $timeout.flush(); 
    }); 
}); 
+0

Non è il 'done()' nel test "uguale a 5" ridondante? '$ timeout.flush()' chiamerà in modo sincrono tutti gli eventi in sospeso registrati in $ timeout, il che causerà la risoluzione della promessa, che chiamerà immediatamente 'expect()'. –

+0

Non ne sono completamente sicuro. Potrebbe valere la pena di fare un test, però. – Beez

+2

@AviCherry Senza il parametro callback 'done', il codice attiverà lo svuotamento ma poi considererà immediatamente il test passato, perché non sa che è stata attivata una chiamata asincrona. Con 'done', ci si assicura che il test non sarà completato finché non viene chiamato' done'. Un 'expect' non finirà un test. Puoi avere più aspettative in un test. Inoltre, se si fornisce il callback "done" ma non lo si esegue mai, il test fallirà perché Jasmine genererà qualcosa come "esecuzione troppo lunga", perché il test non sarà mai completato. Non ricordo il feedback esatto. –

Problemi correlati