Подтвердить что ты не робот

Unit test сервис, который возвращает обещание Angularjs Jasmine

EDITED за сообщение Михала Чаремзы.

У меня есть сервис, представляющий модальный диалог:

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).

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

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

Проблема в том, что я не могу unit test это.

Это мой тест. Я правильно ввел 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... не выполняется. Что мне не хватает?

4b9b3361

Ответ 1

Чтобы высмеять функцию, которая возвращает обещание, она также должна вернуть обещание, которое затем должно быть разрешено как отдельный шаг.

В вашем случае deferred.resolve(), который вы передаете шпиону, нужно заменить на deferred.promise, а отложенное .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');
});

Обычно вы хотите сделать это, если не хотите выставлять внутреннюю работу функции своему вызывающему. Это аналогично концепции повторного выброса исключения в синхронное программирование.