2013-04-18 20 views
65

Mam ciężkie czasy próbować przetestować oparty na obietnicach kod w Angularjs.Kod oparty na testach jednostkowych w Angularjs

Mam następujący kod w moim kontrolera:

$scope.markAsDone = function(taskId) { 
     tasksService.removeAndGetNext(taskId).then(function(nextTask) { 
      goTo(nextTask); 
     }) 
    }; 

    function goTo(nextTask) { 
     $location.path(...); 
    } 

Chciałbym jednostka test następujące przypadki:

  • gdy markAsDone nazywa powinien zadzwonić tasksService.removeAndGetNext
  • kiedy tasksService.removeAndGetNext wykonano, należy zmienić lokalizację (wywołać goTo)

Wydaje mi się, że nie ma łatwego sposobu na sprawdzenie tych dwóch przypadków oddzielnie.

Co zrobiłem, aby przetestować pierwszy był:

var noopPromise= {then: function() {}} 
spyOn(tasksService, 'removeAndGetNext').andReturn(noopPromise); 

teraz przetestować drugi przypadek trzeba utworzyć kolejną fałszywą obietnicę, że zawsze będzie resolved. To wszystko dość uciążliwe i jest dużo kodu standardowego.

Czy istnieje inny sposób sprawdzenia takich rzeczy? A może mój zapach ma zapach?

+0

Przyjęte rozwiązanie nie działa dla mnie. To zrobiło: http://stackoverflow.com/questions/21895684/how-do-i-unit-test-an-angularjs-controller-that-relies-on-a-romrom?rq=1 – sibidiba

Odpowiedz

105

Nadal trzeba będzie kpić z usług i zwrócić obietnicę, ale zamiast tego należy używać prawdziwych obietnic, więc nie trzeba wdrażać jego funkcjonalności. Użyj beforeEach, aby utworzyć spełnioną obietnicę i sfałszować usługę, jeśli potrzebujesz jej ZAWSZE być rozwiązane.

var $rootScope; 

beforeEach(inject(function(_$rootScope_, $q) { 
    $rootScope = _$rootScope_; 

    var deferred = $q.defer(); 
    deferred.resolve('somevalue'); // always resolved, you can do it from your spec 

    // jasmine 2.0 
    spyOn(tasksService, 'removeAndGetNext').and.returnValue(deferred.promise); 

    // jasmine 1.3 
    //spyOn(tasksService, 'removeAndGetNext').andReturn(deferred.promise); 

})); 

Jeśli wolisz wolą rozwiązać go w każdej it bloku z inną wartością, a następnie po prostu wystawiać odroczone do zmiennej lokalnej i rozwiązać go w spec.

Oczywiście, możesz zachować swoje testy, jak to jest, ale tutaj jest kilka naprawdę prostych specyfikacji, aby pokazać, jak to działa.

it ('should test receive the fulfilled promise', function() { 
    var result; 

    tasksService.removeAndGetNext().then(function(returnFromPromise) { 
    result = returnFromPromise; 
    }); 

    $rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle 
    expect(result).toBe('somevalue'); 
}); 
+0

czy możesz wyjaśnić cel z $ rootScope = _ $ rootScope_; w twoim pierwszym przykładzie? – aamiri

+2

To, co robię, to wstrzyknięcie '$ rootScope' (jeśli wstawisz _ przed i po, zostanie to zignorowane) i odsłania je za pomocą zmiennej lokalnej. W ten sposób nie musisz wstrzykiwać go do każdego osobnego speca, ponieważ prawdopodobnie wszystkie z nich będą z niego korzystać. –

+13

+1 Brakowało mi $ rootScope.$ apply() - bardzo pomocne dzięki – Mike

4

Innym rozwiązaniem byłoby dodaje, wzięte prosto z kontrolerem byłem testowania:

var create_mock_promise_resolves = function (data) { 
    return { then: function (resolve) { return resolve(data); }; 
}; 

var create_mock_promise_rejects = function (data) { 
    return { then: function (resolve, reject) { if (typeof reject === 'function') { return resolve(data); } }; 
}; 

var create_noop_promise = function (data) { 
    return { then: function() { return this; } }; 
}; 
+2

Twoja obietnica obiecuje uruchamiać funkcje resolve() lub reject() synchronicznie, ale w AngularJs zawsze jest Sporządzono asynchronicznie. Nie polecam tego rozwiązania, ponieważ nie dokładnie testuje kod. – Kayhadrin

+1

W testach jednostkowych, chcesz mieć możliwość synchronicznego uruchamiania kodu asynchronicznego. Z tego powodu usługi takie jak '$ httpBackend' są automatycznie wyśmiewane w środowisku testowym Angular. – yangmillstheory

+0

W zasadzie całkowicie rozumiem twój punkt widzenia. Te obietnice automatycznie się przepłuczą. Chcesz możliwość ich przepłukać na polecenie. – yangmillstheory

0

I jeszcze inna opcja można uniknąć konieczności wzywania $digest za pomocą biblioteki Q (https://github.com/kriskowal/q) jako zamiennik dla $q np

beforeEach(function() { 
    module('Module', function ($provide) { 
     $provide.value('$q', Q); 
    }); 
}); 

ten sposób obietnice mogą być rozwiązane/odrzucona poza cykl $ digest.

Powiązane problemy