窥探承诺(spyOn with Promise)

我有以下函数,我想窥探...但它包含一个承诺...但我得到TypeError:'undefined'不是一个对象(评估'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(); })

******************* UPDATE

所以我改成它:

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

说实话,虽然我不确定我能否解释一下。 虽然我理解所有异步的东西......我不确定茉莉是如何使用它来使它全部工作的。 任何人都可以解释流量???? 另外我觉得语法错了...你通常会怎么写这个看起来更好/更清洁......?

I have the following function that I would like to spy... but it contains a promise... But I am getting TypeError: 'undefined' is not an object (evaluating 'modalService.showModal({}, modalOptions).then')

Because of course I have just spyOn(modalService,'showModal')

How do I account for the promise too so ??

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

Controller function:

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

Here is my unit test:

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

*******************UPDATE

So I change it to this:

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

To be honest though I am not sure I could explain this. While I understand all the async stuff... I am not sure how jasmine is using this to make it all work. Can anyone explain the flow???? Also I feel the syntax is wrong... how would you typically write this so it looks better/cleaner...??

最满意答案

当您需要模拟返回promise的函数时,您有两个选择:

返回一个模拟的承诺(一个类似于承诺的对象); 回报真正的承诺。

我建议#2因为它更容易,你不必担心复制整个promise 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(); }); });

工作的Plunker

每个测试中的模拟安排代码可以移动到beforeEach部分,因此不会重复。 为了简单起见,我没有这样做。

When you need to mock a function that returns a promise, you have two options:

Return a mocked promise (an object that resembles a promise); Return a real promise.

I suggest #2 because it's easier and you don't have to worry about replicating the whole promise API. In other words, it isn't worth mocking a promise itself.

Now about Jasmine: you only need to use spyOn when you already have an object (not a mock) and you want to spy on (no pun intended) one of its methods. In your case, your whole object is fake, so you could use jasmine.createSpyObj instead.

The following example should make all of the above clearer:

SUT

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

Test

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

That mock arrangement code within each test could be moved into a beforeEach section so it doesn't get duplicated. I didn't do that in order to make things simple.

更多推荐