Código baseado em promise de teste de unidade em Angularjs

Estou tendo dificuldades para testar o código baseado em promise no Angularjs.

Eu tenho o seguinte código no meu controlador:

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

Eu gostaria de testar a unidade nos seguintes casos:

  • quando markAsDone é chamado, deve chamar tasksService.removeAndGetNext
  • Quando tasksService.removeAndGetNext é feito, deve mudar de localização (invocar goTo )

Parece-me que não há maneira fácil de testar esses dois casos separadamente.

O que eu fiz para testar o primeiro foi:

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

Agora, para testar o segundo caso, preciso criar outra promise falsa que seria sempre resolved . É tudo muito entediante e é um monte de código clichê.

Existe alguma outra maneira de testar essas coisas? Ou o meu design cheira?

Você ainda precisará zombar dos serviços e retornar uma promise, mas deve usar promises reais, para não precisar implementar sua funcionalidade. Use beforeEach para criar a promise já cumprida e zombar do serviço se você precisar que SEMPRE seja resolvido.

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

Se você preferir resolvê-lo em cada bloco com um valor diferente, exponha o diferido a uma variável local e resolva-o na especificação.

Naturalmente, você manteria seus testes como estão, mas aqui estão algumas especificações realmente simples para mostrar como isso funcionaria.

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

Outra abordagem seria a seguinte, retirada diretamente de um controlador que eu estava testando:

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

E como ainda outra opção, você pode evitar ter que chamar $digest usando a biblioteca Q ( https://github.com/kriskowal/q ) como um substituto para $q por exemplo:

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

Desta forma, as promises podem ser resolvidas / rejeitadas fora do ciclo $ digest.