Единичный код на основе обещаний в Angularjs
У меня тяжелые времена пытаются проверить код на основе обещаний в Angularjs.
В моем контроллере есть следующий код:
$scope.markAsDone = function(taskId) {
tasksService.removeAndGetNext(taskId).then(function(nextTask) {
goTo(nextTask);
})
};
function goTo(nextTask) {
$location.path(...);
}
Я хотел бы выполнить тестирование следующих случаев:
- Когда
markAsDone
вызывается, он должен вызывать tasksService.removeAndGetNext
- Когда
tasksService.removeAndGetNext
выполняется, он должен изменить местоположение (invoke goTo
)
Мне кажется, что нет простого способа проверить эти два случая отдельно.
Что я сделал для проверки первого:
var noopPromise= {then: function() {}}
spyOn(tasksService, 'removeAndGetNext').andReturn(noopPromise);
Теперь, чтобы проверить второй случай, мне нужно создать еще одно поддельное обещание, которое всегда было бы resolved
. Все это довольно утомительно, и у него много шаблонов.
Есть ли другой способ проверить такие вещи? Или мой дизайн запах?
Ответы
Ответ 1
Вам все равно нужно высмеивать службы и возвращать обещание, но вместо этого вы должны использовать реальный promises, поэтому вам не нужно реализовывать его функциональность. Используйте beforeEach
, чтобы создать уже выполненное обещание и издеваться над сервисом, если вам нужно его ВСЕГДА быть разрешены.
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);
}));
Если вы предпочитаете разрешать его в каждом блоке it
с другим значением, вы просто выставляете отложенную локальную переменную и разрешаете ее в спецификации.
Конечно, вы бы сохранили свои тесты, как есть, но вот некоторые действительно простые спецификации, чтобы показать вам, как это будет работать.
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');
});
Ответ 2
Другим подходом было бы следующее, взятое прямо из контроллера, который я тестировал:
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; } };
};
Ответ 3
И как еще один вариант, вы можете избежать вызова $digest
с помощью библиотеки Q (https://github.com/kriskowal/q) вместо $q
например:
beforeEach(function () {
module('Module', function ($provide) {
$provide.value('$q', Q);
});
});
Этот способ promises может быть разрешен/отклонен за пределами цикла $digest.