2013-07-02 4 views
43

Ich habe eine Funktion innerhalb einer meiner Winkel Dienste, die Ich mag würde immer wieder in regelmäßigen Abständen aufgerufen werden. Ich würde das gerne mit $ timeout machen. Es sieht etwa so aus:

var interval = 1000; // Or something 

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

_tick(); 

ich, wie man Unit-Test zur Zeit mit Jasmin diese ratlos bin - Wie kann ich das tun? Wenn ich $timeout.flush() verwende, dann treten die Funktionsaufrufe auf unbestimmte Zeit auf. Wenn ich Jasmins Scheinuhr benutze, scheint $timeout davon unberührt zu sein. Grundsätzlich, wenn ich diese Arbeit zu bekommen, sollte ich gut zu gehen:

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(); 
    }); 
}); 

Diese beiden Varianten arbeiten, aber mir nicht helfen:

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(); 
    }); 
}); 

Vielen Dank im Voraus!

+0

Versuchen '$ rootScope' Injektion und ruft' $ rootScope. Anwenden $() 'nach der Uhr nach vorne schieben. –

Antwort

51

Lassen Sie Ihren Test Async nicht von Jasmins der Uhr. Verwenden Sie stattdessen $timeout.flush(), um den Testablauf synchron zu halten. Es mag ein bisschen schwierig zu installieren, aber sobald Sie es bekommen, werden Ihre Tests schneller und kontrollierter sein.

Hier ist ein Beispiel für einen Test, der es mit diesem Ansatz funktioniert: https://github.com/angular/angular.js/blob/master/test/ngAnimate/animateSpec.js#L618

+0

Ihre Antwort löst das Problem, wie auch eine wichtige Faustregel bekräftigt: Unit-Tests sollten immer synchron sein – eitanfar

44

@ Matsko Antwort führte mich auf den richtigen Weg nach unten. Ich dachte, ich würde meine "vollständige" Lösung veröffentlichen, um die Antwort einfacher zu finden.

Die Sache zu prüfen

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

Der 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

Ist das nicht der 'getan()' in der „gleich 5“ Test überflüssig? '$ timeout.flush()' ruft synchron alle ausstehenden Ereignisse auf, die in $ timeout registriert sind, wodurch das Versprechen aufgelöst wird und sofort 'expect() 'aufgerufen wird. –

+0

Ich bin mir nicht ganz sicher. Könnte einen Test wert sein. – Beez

+2

@AviCherry Ohne den Callback-Parameter 'done' wird der Code den Flush auslösen, aber dann sofort den Test als bestanden betrachten, weil er nicht weiß, dass ein asynchroner Aufruf ausgelöst wurde. Mit 'done' stellen Sie sicher, dass der Test nicht abgeschlossen wird, bis' done' aufgerufen wird. Ein 'expect' wird einen Test nicht beenden. Sie können in einem Test mehrere Erwartungen haben. Auch, wenn Sie den 'done' Rückruf zur Verfügung stellen, aber sie es nie ausführen, wird Ihr Test nicht bestehen, weil Jasmin so etwas wie' Ausführung zu long' nahm werfen, weil der Test wird nie abgeschlossen sein. Kann mich nicht an die genaue Rückmeldung erinnern. –