J'ai du mal à tester le code basé sur les promesses dans Angularjs.
J'ai le code suivant dans mon contrôleur:
$scope.markAsDone = function(taskId) {
tasksService.removeAndGetNext(taskId).then(function(nextTask) {
goTo(nextTask);
})
};
function goTo(nextTask) {
$location.path(...);
}
Je voudrais tester de manière unitaire les cas suivants:
markAsDone
est appelé, il doit appeler tasksService.removeAndGetNext
tasksService.removeAndGetNext
est fait, il doit changer d'emplacement (invoquer goTo
)Il me semble qu'il n'y a pas de moyen facile de tester ces deux cas séparément.
Ce que j'ai fait pour tester le premier était:
var noopPromise= {then: function() {}}
spyOn(tasksService, 'removeAndGetNext').andReturn(noopPromise);
Maintenant, pour tester le deuxième cas, j'ai besoin de créer une autre fausse promesse qui serait toujours resolved
. C'est assez fastidieux et c'est beaucoup de code passe-partout.
Existe-t-il un autre moyen de tester de telles choses? Ou mon design sent-il?
Vous devrez toujours vous moquer des services et renvoyer une promesse, mais vous devez utiliser de vraies promesses à la place, vous n'avez donc pas besoin d'implémenter ses fonctionnalités. Utilisez beforeEach
pour créer la promesse déjà remplie et moquez le service si vous en avez besoin pour TOUJOURS être résolu.
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);
}));
Si vous préférez le résoudre dans chaque bloc it
avec une valeur différente, vous exposez simplement le différé à une variable locale et le résolvez dans la spécification.
Bien sûr, vous garderiez vos tests tels quels, mais voici quelques spécifications très simples pour vous montrer comment cela fonctionnerait.
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');
});
Une autre approche serait la suivante, prise directement à partir d'un contrôleur que je testais:
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; } };
};
Et comme encore une autre option, vous pouvez éviter d'avoir à appeler $digest
en utilisant la bibliothèque Q ( https://github.com/kriskowal/q ) en remplacement de $q
par exemple:
beforeEach(function () {
module('Module', function ($provide) {
$provide.value('$q', Q);
});
});
De cette façon, les promesses peuvent être résolues/rejetées en dehors du cycle $ digest.