返回承诺Angularjs Jasmine的单元测试服务

时间:2014-04-07 07:02:24

标签: angularjs jasmine promise

根据Michal Charemza的帖子编辑。

我有一个代表angularui模态对话框的服务:

app.factory("dialogFactory", function($modal, $window, $q) {

    function confirmDeleteDialog() {

    var modalInstance = $modal.open({
        templateUrl: "../application/factories/confirmDeleteDialog.htm",
        controller: function($scope, $modalInstance) {

            $scope.ok = function() {
                $modalInstance.close("true");
            };

            $scope.cancel = function() {
                $modalInstance.dismiss("false");
            };
        }
    });


    return modalInstance.result.then(function(response) {
        return 'My other success result';
    }, function(response) {
        return $q.reject('My other failure reason');
    });

};

    return {
        confirmDeleteDialog: confirmDeleteDialog
    };

});

如果用户已从对话框requestNotificationChannel.deleteMessage(id)中单击确定,则调用delete方法。

$scope.deleteMessage = function(id) {
        var result = dialogFactory.confirmDeleteDialog();

        result.then(function(response) {
            requestNotificationChannel.deleteMessage(id);
        });
    };

问题是我无法对此进行单元测试。

这是我的考试。我已经正确地注入了q服务,但我不确定应该从"confirmDeleteDialog" spy ...

返回什么
describe("has a delete method that should call delete message notification", function() {
            var deferred = $q.defer();
            spyOn(dialogFactory, "confirmDeleteDialog").and.returnValue(deferred.promise);

            spyOn(requestNotificationChannel, "deleteMessage");

            $scope.deleteMessage(5);
            deferred.resolve();

            it("delete message notification is called", function() {
                expect(requestNotificationChannel.deleteMessage).toHaveBeenCalled();
            });
        });

但我收到了expected spy deleteMessage to have been called。这意味着result.then ...部分未执行。我错过了什么?

1 个答案:

答案 0 :(得分:40)

要模拟一个返回promise的函数,它还需要返回一个promise,然后需要将其解析为一个单独的步骤。

在您的情况下,传递给间谍的deferred.resolve()需要替换为deferred.promise,而deferred.resolve()需要单独执行。

beforeEach(function() {
  var deferred = $q.defer();
  spyOn(dialogFactory, "confirmDeleteDialog").and.returnValue(deferred.promise);
  spyOn(requestNotificationChannel, "deleteMessage");
  $scope.deleteMessage(5);
  deferred.resolve();
  $rootScope.$digest();
});

it("delete message notification is called", function() {
  expect(requestNotificationChannel.deleteMessage).toHaveBeenCalled();
});

我怀疑您还需要致电$rootScope.$digest(),因为Angular的承诺实施与摘要循环相关联。

此外,与您的问题略有不同,但我认为您不需要在confirmDeleteDialog中创建自己的延迟对象。您使用的(反)模式已标记为“遗忘承诺”,如http://taoofcode.net/promise-anti-patterns/

什么时候更简单,使用更少的代码,我认为这样可以更好地处理错误,您只需返回$modal服务创建的承诺:

var modalInstance = $modal.open({...});
return modalInstance.result;

如果要根据已解析/拒绝的值修改调用函数所看到的内容,可以通过返回then的结果来创建链式承诺:

var modalInstance = $modal.open({...});
return modalInstance.result.then(function(successResult) {
  return 'My other success result';
}, function(failureReason) {
  return $q.reject('My other failure reason');
});

如果您不想将函数的内部工作暴露给其调用者,通常会这样做。这类似于在同步编程中重新抛出异常的概念。