2013-04-18 8 views
65

Ich habe harte Zeiten versuchen, versprechen-basierten Code in Angularjs zu testen.Unit-Test-Versprechen-basierten Code in Angularjs

Ich habe den folgenden Code in meinem Controller:

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

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

ich folgende Fälle zu Unit-Test möchten:

  • wenn markAsDone es nennen sollte
  • tasksService.removeAndGetNext genannt wird
  • wenn tasksService.removeAndGetNext ist fertig, sollte es den Standort wechseln (goTo aufrufen)

Es scheint mir, dass es keinen einfachen Weg gibt, diese beiden Fälle getrennt zu testen.

Was ich tat, um den ersten zu testen war:

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

nun den zweiten Fall zu testen ich eine andere gefälschte Versprechen erstellen müssen, die immer resolved sein würde. Es ist alles ziemlich langweilig und es ist eine Menge Standardcode.

Gibt es eine andere Möglichkeit, solche Dinge zu testen? Oder riecht mein Design?

+0

Die akzeptierte Lösung funktionierte nicht für mich. Dies tat: http://StackOverflow.com/Questions/21895684/How-Do-I-Unit-test-an-angularjs-Controller-that-Relies-on-Apromise?RQ=1 – sibidiba

Antwort

105

Sie müssen immer noch die Dienste verspotten und ein Versprechen zurückgeben, aber Sie sollten stattdessen echte Versprechen verwenden, so dass Sie seine Funktionalität nicht implementieren müssen. Verwenden Sie beforeEach, um das bereits erfüllte Versprechen zu erstellen und den Dienst zu verspotten, wenn Sie IMMER gelöst werden müssen.

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

})); 

Wenn Sie lieber mit einem anderen Wert in jedem it Block lösen würden es vorziehen, dann setzen Sie nur die auf eine lokale Variable abgegrenzt und es in der Spezifikation lösen.

Natürlich würden Sie Ihre Tests behalten, wie sie sind, aber hier ist eine wirklich einfache Spezifikation, um Ihnen zu zeigen, wie es funktionieren würde.

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

können Sie den Zweck erklären von $ rootScope = _ $ rootScope_; in deinem ersten Beispiel? – aamiri

+2

Was ich mache, ist die Injektion des '$ rootScope' (wenn Sie ein _ vorher und nachher einfügen, wird es ignoriert) und es durch eine lokale Variable offen legen. Auf diese Weise müssen Sie es nicht in jede einzelne Spezifikation einfügen, da sie wahrscheinlich alle verwenden werden. –

+13

+1 Ich habe dieses $ rootScope vermisst.$ apply() - sehr hilfreich danke – Mike

4

Ein anderer Ansatz wäre die folgenden, gerade ich Tests war aus einem Controller genommen werden:

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

Ihr Versprechenspseud wird die resolve() - oder reject() -Funktionen synchron ausführen, aber in AngularJs ist es immer so asynchron gemacht. Also ich würde diese Lösung nicht empfehlen, weil es den Code nicht genau prüft. – Kayhadrin

+1

In Komponententests möchten Sie asynchronen Code synchron ausführen können. Aus diesem Grund werden Dienste wie '$ httpBackend' in der Angular-Testumgebung automatisch ausgelöscht. – yangmillstheory

+0

Eigentlich sehe ich deinen Standpunkt völlig. Diese Versprechen werden sich automatisch ausspülen. Sie möchten die Fähigkeit, sie auf Befehl zu leeren. – yangmillstheory

0

Und noch eine weitere Option können Sie vermeiden, indem Sie mit der Q-Bibliothek nennen $digest mit (https://github.com/kriskowal/q) als Drop-in-Ersatz für $q zB:

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

auf diese Weise verspricht gelöst werden kann/abgelehnt außerhalb der $ Digest-Zyklus.

Verwandte Themen