webentwicklung-frage-antwort-db.com.de

Versprechungsbasierter Unit-Test-Code in Angularjs

Es fällt mir schwer, versprechungsbasierten Code in Angularjs zu testen.

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 möchte die folgenden Fälle einem Unit-Test unterziehen:

  • wenn markAsDone aufgerufen wird, sollte es tasksService.removeAndGetNext aufrufen.
  • wenn tasksService.removeAndGetNext fertig ist, sollte sich der Ort ändern (rufe goTo auf)

Es scheint mir, dass es keine einfache Möglichkeit gibt, diese beiden Fälle getrennt zu testen.

Was ich getan habe, um den ersten zu testen, war:

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

Um den zweiten Fall zu testen, muss ich ein weiteres falsches Versprechen erstellen, das immer resolved lautet. Es ist alles ziemlich langweilig und es ist eine Menge Kesselcode.

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

67

Sie müssen die Services weiterhin verspotten und ein Versprechen zurückgeben, aber Sie sollten stattdessen echte Versprechen verwenden, damit Sie die Funktionalität nicht implementieren müssen. Verwenden Sie beforeEach, um das bereits erfüllte Versprechen zu erstellen und den Service zu verspotten, wenn Sie ihn IMMER lösen 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 es lieber vorziehen, es in jedem it Block mit einem anderen Wert aufzulösen, dann legen Sie das Verschobene einfach in einer lokalen Variablen offen und lösen es in der Spezifikation auf.

Natürlich würden Sie Ihre Tests so lassen, wie sie sind, aber hier ist eine wirklich einfache Spezifikation, die Ihnen zeigt, 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');
});
107
Caio Cunha

Ein anderer Ansatz wäre der folgende, der direkt aus einem von mir getesteten Controller stammt:

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; } };
};
4
yangmillstheory

Und als weitere Option können Sie vermeiden, $digestdurch Verwendung der Q-Bibliothek ( https://github.com/kriskowal/q ) als Ersatz für $q z.B:

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

Auf diese Weise können Versprechungen außerhalb des $ digest-Zyklus gelöst/abgelehnt werden.

0
Michael