间谍与承诺

spyOn with Promise

我有以下我想监视的函数...但它包含一个承诺...但我收到 TypeError: 'undefined' is not an object (evaluating 'modalService.showModal({}, modalOptions).then')

因为我当然只有 spyOn(modalService,'showModal')

诺言也这样怎么算??

_modalService = {
    close: function (value) { console.log(value) },
    dismiss: function (value) { console.log(value) },
    showModal: function (value) { console.log(value) }
};

spyOn(_modalService, 'close');
spyOn(_modalService, 'dismiss');
spyOn(_modalService, 'showModal');

控制器函数:

user.resetPassword = function () {
            var modalOptions = {
                closeButtonText: 'Cancel',
                actionButtonText: 'Reset',
                headerText: 'Reset Password',
                bodyText: 'Are you sure you want to reset the users password?'
            };

            modalService.showModal({}, modalOptions).then(function (result) {
                if (result === 'ok') {
                    userDataService.resetPassword(user.data).then(function (result) {
                        $scope.$emit('showSuccessReset');
                    });

                };
            });
        };

这是我的单元测试:

it('should allow the users password to be reset', function () {
        var controller = createController();
        controller.resetPassword();
        $httpBackend.flush();
    })

*********************更新

所以我改成这样:

 //Create a fake instance of the modal instance. TO ensure that the close is called
        _modalService = {
            close: function (value) { console.log(value) },
            dismiss: function (value) { console.log(value) },
            showModal: function (value) { console.log(value) }
        };

        spyOn(_modalService, 'close');
        spyOn(_modalService, 'dismiss');
        spyOn(_modalService, 'showModal').and.callThrough();

        _modalService.showModal = function() {
                var deferred = $q.defer();
                deferred.resolve('Remote call result');
                return deferred.promise;   
            };

说实话,虽然我不确定我能否解释清楚。虽然我理解所有异步的东西......我不确定茉莉花是如何使用它来使它全部工作的。谁能解释一下流程????我也觉得语法不对...你通常会怎么写它看起来 better/cleaner...??

当你需要模拟一个 returns promise 的函数时,你有两个选择:

  1. Return 一个模拟的承诺(一个类似于承诺的对象);
  2. Return 一个真正的承诺。

我建议 #2,因为它更容易,而且您不必担心复制整个承诺 API。换句话说,嘲笑承诺本身是不值得的。

关于 Jasmine:只有当您已经有了一个对象(不是模拟对象)并且您想监视(没有双关语意)它的一个方法时,您才需要使用 spyOn。在您的情况下,您的整个对象都是假的,因此您可以改用 jasmine.createSpyObj

下面的例子应该会使以上所有内容更加清楚:

SUT

app.controller('MainCtrl', function($scope, modal, service) {
  $scope.click = function() {
    modal.show().then(function(result) {
      if (result === 'ok') {
        service.resetPassword();
      }
    });
  };
});

测试

describe('Testing a controller', function() {
  var $scope, $q,
      ctrl, modalMock, serviceMock;

  beforeEach(function() {
    module('plunker');

    modalMock = jasmine.createSpyObj('modal', ['show']);
    serviceMock = jasmine.createSpyObj('service', ['resetPassword']);

    inject(function($rootScope, $controller, _$q_) {
      $scope = $rootScope.$new();
      $q = _$q_;

      ctrl = $controller('MainCtrl', {
        $scope: $scope,
        modal: modalMock,
        service: serviceMock
      });
    });
  });

  it('should reset the password when the user confirms', function() {
    // Arrange
    var deferred = $q.defer();

    deferred.resolve('ok');
    modalMock.show.and.returnValue(deferred.promise);

    // Act
    $scope.click();
    $scope.$digest(); // Makes Angular resolve the promise

    // Assert
    expect(serviceMock.resetPassword).toHaveBeenCalled();
  });

  it('should not reset the password when the user cancels', function() {
    // Arrange
    var deferred = $q.defer();

    deferred.resolve('cancel');
    modalMock.show.and.returnValue(deferred.promise);

    // Act
    $scope.click();
    $scope.$digest(); // Makes Angular resolve the promise

    // Assert
    expect(serviceMock.resetPassword).not.toHaveBeenCalled();
  });
});

Working Plunker

可以将每个测试中的模拟编排代码移至 beforeEach 部分,以免重复。我这样做不是为了让事情简单化。