web-dev-qa-db-fra.com

Code basé sur la promesse de test unitaire dans Angularjs

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:

  • lorsque markAsDone est appelé, il doit appeler tasksService.removeAndGetNext
  • quand 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?

67
Michal Ostruszka

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');
});
107
Caio Cunha

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

Et comme encore une autre option, vous pouvez éviter d'avoir à appeler $digesten 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.

0
Michael