2014-04-17 21 views
30

Próbowałem zawrzeć moje obietnice Jasmine 2.0 i AngularJS. Wiem, że:Testowanie obietnic AngularJS w Jasmine 2.0

Jak mogę przetestować angularjs obietnic pomocą nowej składni asynchronicznej w Jasmine 2.0?

+1

Jeśli testowanie obiecuje, dlaczego w ogóle chcesz używać składni async, zarówno w starej, jak i nowej Jasmine? Czy możesz opublikować funkcję, którą próbujesz przetestować? W wielu przypadkach można użyć '$ timeout.flush()' i/lub 'myPromise.resolve()' w testach, aby wymusić synchronizację. –

+1

@MichalCharemza Ku mojemu zaskoczeniu, '$ timeout.flush()' działa świetnie bez użycia 'done' Jasmine; Nie mam funkcji do zademonstrowania, kiedy potrzebuję 'done' +' $ timeout.flush() '. Co jeśli testy wykorzystują np. rzeczywisty backend '$ http'? To jest (oczywiście) lepiej kpić z '$ http' dla szybkości, ale czy' $ timeout.flush() 'bez' done' tam działa? Czy opóźnione rozwiązanie obietnicy robi jakąkolwiek różnicę? –

+1

Jeśli testy używają rzeczywistego backendu '$ http', łączącego się z prawdziwym serwerem, wówczas test byłby asynchroniczny i trzeba użyć' done'. '$ timeout.flush()' (lub '$ httpBackend.flush()') wpływa na kod działający lokalnie: nie można wywołać funkcji, aby zażądać od serwera odpowiedzi na żądanie już teraz! Jeśli nie masz pewności, jak przetestować określoną funkcję (powiedzmy, używając '$ timeout' lub' $ http'), możesz umieścić tę funkcję w pytaniu). –

Odpowiedz

42

Po wywołaniu promise.resolve():

  • Wezwania $timeout.flush(). Spowoduje to wymuszenie cyklu skracania i rozpowszechnienie obiecanej rozdzielczości
  • Zadzwoń pod done(). To mówi Jasmine testy asynchroniczny zakończyły

Oto przykład (Demo on Plunker):

describe('AngularJS promises and Jasmine 2.0', function() { 
    var $q, $timeout; 

    beforeEach(inject(function(_$q_, _$timeout_) { 
     // Set `$q` and `$timeout` before tests run 
     $q = _$q_; 
     $timeout = _$timeout_; 
    })); 

    // Putting `done` as argument allows async testing 
    it('Demonstrates asynchronous testing', function(done) { 
     var deferred = $q.defer(); 

     $timeout(function() { 
      deferred.resolve('I told you I would come!'); 
     }, 1000); // This won't actually wait for 1 second. 
        // `$timeout.flush()` will force it to execute. 

     deferred.promise.then(function(value) { 
      // Tests set within `then` function of promise 
      expect(value).toBe('I told you I would come!'); 
     }) 
     // IMPORTANT: `done` must be called after promise is resolved 
     .finally(done); 

     $timeout.flush(); // Force digest cycle to resolve promises 
    }); 
}); 
+2

Po prostu chciałem dodać link do wpisu K. Scott Allena na temat techniki wstrzykiwania beforeEach, której używasz tutaj: http://odetocode.com/blogs/scott/archive/2014/05/15/a-few-thoughts- on-better-unit-tests-for-angularjs-controllers.aspx – zen

+6

przegłosowane, poważnie, dodanie tylko $ timeout.flush() naprawia moją specyfikację. Naprawdę szkoda, że ​​nie jest tak dobrze udokumentowane ... –

+0

Sporządzono nie jest konieczne powyżej, wszystkie zadania są wykonywane przez $ timeout.flush() –

4

Dla mnie $timeout.flush() nie działa bardzo dobrze, ale mam wiele async połączeń w moim spec. Znalazłem $rootScope.$apply(), jako metodę wymuszania digest na każdym wywołaniu asynchronicznym.

describe('AngularJS promises and Jasmine 2.0', function() { 
    beforeEach(inject(function (_$q_, _$timeout_, _$rootScope_) { 
    $q = _$q_ 
    $timeout = _$timeout_ 
    $rootScope = _$rootScope_ 
    })) 

    it('demonstrates asynchronous testing', function (done) { 
    var defer = $q.defer() 

    Async.call() 
    .then(function (response) { 
     // Do something 

     var d = $q.defer() 
     Async.call() 
     .then(function (response) { 
     d.resolve(response) 
     $rootScope.$apply() // Call the first digest 
     }) 
     return d.promise 
    }) 
    .then(function (response) { 
     // Do something after the first digest 

     Async.call() 
     .then(function (response) { 
     defer.resolve(response) // The original defer 
     $rootScope.$apply() // Call the second digest 
     }) 
    }) 

    defer.promise.then(function(value) { 
     // Do something after the second digest 
     expect(value).toBe('I told you I would come!') 
    }) 
    .finally(done) 

    if($timeout.verifyNoPendingTasks()) 
     $timeout.flush() 
    }) 
}) 

To jest jak powiązane rzeczy asynchroniczne. Mam nadzieję, że pomoże to w rozmowie. Pozdrawiam

3

Ta odpowiedź nie doda nic nowego do powyższych, ma jedynie na celu wyartykułowanie odpowiedzi w bardziej szczegółowy sposób, ponieważ zadziałało właśnie dla mnie. Kiedy pojawił się problem opisany w pytaniu powyżej, spędziłem wiele czasu próbując znaleźć sposób, aby upewnić się, że wszystkie obietnice miały czas na zakończenie i wszystkie twierdzenia zostały potwierdzone.

W moim przypadku miałem serię obietnic, a po każdym muszę upewnić się, że wyniki odpowiadają moim oczekiwaniom. Nie utworzyłem żadnej obietnicy przy użyciu deferred, raczej powoływałem się na istniejące.

Chodzi o to, że $timeout.flush() był dla mnie całkowicie wystarczający. Moja próba pracy wygląda następująco:

describe("Plain command without side-effects", function() { 
    it("All usecases", inject(function($timeout) { 
     console.log("All together"); 
     expect(state.number).toEqual(1); 
     cmdHistory 
      .execute(increaseState, decreaseState) 
      .then(function() { 
       console.log("Execute works"); 
       expect(state.number).toEqual(2); 
       return cmdHistory.redo(); // can't redo, nothing's undone 
      }) 
      .then(function() { 
       console.log("Redo would not work"); 
       expect(state.number).toEqual(2); 
       return cmdHistory.undo(); 
      }) 
      .then(function() { 
       console.log("Undo undoes"); 
       expect(state.number).toEqual(1); 
       return cmdHistory.undo(); 
      }) 
      .then(function() { 
       console.log("Next undo does nothing"); 
       expect(state.number).toEqual(1); 
       return cmdHistory.redo(); // but still able to redo 

      }) 
      .then(function() { 
       console.log("And redo redoes neatly"); 
       expect(state.number).toEqual(2); 
      }); 

     $timeout.flush(); 
    })); 

Test ten przeznaczony jest, aby upewnić się, że commandHistory obiekt działa poprawnie, to musi działań: execute i unExecute i trzech metod: execute, undo, redo, którego zwrot wszystkich obietnice.

Bez numeru $timeout.flush(), wszystkie dane wyjściowe dziennika miały wartość All together i nie zawierały dalszych komunikatów dziennika.Dodawanie $timeout.flush() ustaliła wszystkiego, a teraz mam wszystkie komunikaty pokazane i wszystkie twierdzenia wykonany

UPDATE Jest jeszcze jedna opcja: można napisać swój zestaw testowy bez łączenia obietnic then, ale po prostu płukania po każdym obietnica została nazywa, tak aby upewnić się, że kończy:

it("All usecases 2", inject(function($timeout) { 
     console.log("All usecases 2"); 
     expect(state.number).toEqual(1); 

     console.log("Execute works"); 
     cmdHistory.execute(increaseState, decreaseState); 
     $timeout.flush(); 
     expect(state.number).toEqual(2); 

     console.log("Redo would not work"); 
     cmdHistory.redo(); // can't redo, nothing's undone 
     $timeout.verifyNoPendingTasks(); 
     expect(state.number).toEqual(2); 

     console.log("Undo undoes"); 
     cmdHistory.undo(); 
     $timeout.flush(); 
     expect(state.number).toEqual(1); 

     console.log("Next undo does nothing"); 
     cmdHistory.undo(); 
     $timeout.verifyNoPendingTasks(); 
     expect(state.number).toEqual(1); 

     console.log("And redo redoes neatly"); 
     cmdHistory.redo(); // but still able to redo 
     $timeout.flush(); 
     expect(state.number).toEqual(2); 
    })); 

Proszę zwrócić uwagę na to, w niektórych przypadkach, gdy moje metody, takie jak undo i redo nie zwracają obietnicę, wzywam $timeout.verifyNoPendingTasks() zamiast flush. Trudno powiedzieć, czy to dobrze, czy źle.

Jednak w tym przypadku test wygląda bardziej sensownie i znacznie prościej.

Powiązane problemy