Unit testing AngularJS单元测试中的模拟$modal
我正在为一个控制器编写一个单元测试,它启动一个Unit testing AngularJS单元测试中的模拟$modal,unit-testing,angularjs,angular-ui-bootstrap,Unit Testing,Angularjs,Angular Ui Bootstrap,我正在为一个控制器编写一个单元测试,它启动一个$modal,并使用返回的承诺执行一些逻辑。我可以测试触发$modal的父控制器,但我一辈子都搞不清楚如何模拟成功的承诺 我尝试了很多方法,包括使用$q和$scope.$apply()强制解决承诺。然而,我得到的最接近的答案是把一些类似于SO post中最后一个答案的东西放在一起 我在“old”$dialog模式中见过几次这样的问题。 我找不到太多关于如何使用“新建”$dialog模式的信息 一些指点会很感激的 为了说明这个问题,我使用了,并做了一些
$modal
,并使用返回的承诺执行一些逻辑。我可以测试触发$modal的父控制器,但我一辈子都搞不清楚如何模拟成功的承诺
我尝试了很多方法,包括使用$q
和$scope.$apply()
强制解决承诺。然而,我得到的最接近的答案是把一些类似于SO post中最后一个答案的东西放在一起
我在“old”$dialog
模式中见过几次这样的问题。
我找不到太多关于如何使用“新建”$dialog
模式的信息
一些指点会很感激的
为了说明这个问题,我使用了,并做了一些小的修改
控制器(主控制器和模式控制器)
视图(main.html)
在beforeach中监视$modal.open函数时
spyOn($modal, 'open').andReturn(fakeModal);
or
spyOn($modal, 'open').and.returnValue(fakeModal); //For Jasmine 2.0+
您需要返回$modal.open通常返回的模拟,而不是$modal的模拟,它不包括您在fakeModal
mock中设置的open
函数。伪模式必须有一个结果
对象,该对象包含一个然后
函数来存储回调(在单击确定或取消按钮时调用)。它还需要一个关闭
功能(模拟在模式上单击OK按钮)和解除
功能(模拟在模式上单击Cancel按钮)。关闭
和关闭
函数在调用时调用必要的回调函数
将fakeModal
更改为以下内容,单元测试将通过:
var fakeModal = {
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
此外,您可以通过在cancel处理程序中添加要测试的属性来测试cancel对话框,在本例中为$scope.cancelled
:
$scope.modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$scope.canceled = true; //Mark the modal as canceled
$log.info('Modal dismissed at: ' + new Date());
});
一旦设置了cancel标志,单元测试将如下所示:
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
scope.open(); // Open the modal
scope.modalInstance.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});
布兰特的回答显然很棒,但这一改变让我感觉更好:
fakeModal =
opened:
then: (openedCallback) ->
openedCallback()
result:
finally: (callback) ->
finallyCallback = callback
然后在测试区域:
finallyCallback()
expect (thing finally callback does)
.toEqual (what you would expect)
为了补充Brant的答案,这里有一个稍微改进的mock,可以让您处理其他一些场景
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
return this;
},
catch: function (cancelCallback) {
this.cancelCallback = cancelCallback;
return this;
},
finally: function (finallyCallback) {
this.finallyCallback = finallyCallback;
return this;
}
},
close: function (item) {
this.result.confirmCallBack(item);
},
dismiss: function (item) {
this.result.cancelCallback(item);
},
finally: function () {
this.result.finallyCallback();
}
};
这将允许模拟处理以下情况
将模态与.then()
、.catch()
和.finally()
处理程序样式一起使用,而不是将两个函数(successCallback,errorCallback
)传递给。然后()
,例如:
modalInstance
.result
.then(function () {
// close hander
})
.catch(function () {
// dismiss handler
})
.finally(function () {
// finally handler
});
既然使用承诺,你肯定应该使用这些东西
代码变为:
function FakeModal(){
this.resultDeferred = $q.defer();
this.result = this.resultDeferred.promise;
}
FakeModal.prototype.open = function(options){ return this; };
FakeModal.prototype.close = function (item) {
this.resultDeferred.resolve(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
FakeModal.prototype.dismiss = function (item) {
this.resultDeferred.reject(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
// ....
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
fakeModal = new FakeModal();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$modal: fakeModal
});
}));
// ....
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
fakeModal.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});
明亮的非常感谢。我完全错过了open函数实际返回的内容,我试图模拟$modal本身。这很有道理。我已经为此奋斗了很久,现在可以看到前进的道路。谢谢。不客气,我很高兴它对你有用。希望UI引导将提供一个默认的$modal mock,我们可以在将来使用它代码>在模式的结果中,我有一个服务调用
SessionService.set('lang',selectedItem)代码>。是否可以测试服务是否在scope.modalInstance.close('FR')之后被调用代码>?@lightalex您可以在服务的“set”功能上使用Jasmine spy,然后期望它已被调用。与此类似:spyOn(SessionService,'set')。和callthrough();范围.modalInstance.close('FR');expect(SessionService.set).toHaveBeenCalled()代码>在这方面遇到一些问题。我的scope.modalInstance未定义..你们得到的是有效的modalInstance吗?嗨,如果我想测试modalInstance控制器(在本例中是ModalInstanceCtrl),那么最好的方法是什么?Itsak:我把你们的评论变成了一个完整的问题。我也被困在这上面了。问题是:我的5美分和茉莉花>=2你应该使用间谍($modal,'open')。和.callFake(fakeModal);这对我来说很有用,是模拟Promise的最好方法。FakeModal中没有定义$rootScope,那么如何在close和dimiss函数中访问它呢?抱歉,我是Angular和Jasmine的新手,我觉得这与作用域继承有关,但我看不出FakeModal是如何获得它的。我认为您的arrow函数中缺少了-可以说,这会使->
变成=>
,对吗?我想您要添加的是,他可以将close:function(item){this.result.confirmCallBack(item);},
替换为close:this.result.confirmCallBack,
,对吗?我不确定第一个代码片段中编写的代码在做什么。(☉_☉)
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
return this;
},
catch: function (cancelCallback) {
this.cancelCallback = cancelCallback;
return this;
},
finally: function (finallyCallback) {
this.finallyCallback = finallyCallback;
return this;
}
},
close: function (item) {
this.result.confirmCallBack(item);
},
dismiss: function (item) {
this.result.cancelCallback(item);
},
finally: function () {
this.result.finallyCallback();
}
};
modalInstance
.result
.then(function () {
// close hander
})
.catch(function () {
// dismiss handler
})
.finally(function () {
// finally handler
});
function FakeModal(){
this.resultDeferred = $q.defer();
this.result = this.resultDeferred.promise;
}
FakeModal.prototype.open = function(options){ return this; };
FakeModal.prototype.close = function (item) {
this.resultDeferred.resolve(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
FakeModal.prototype.dismiss = function (item) {
this.resultDeferred.reject(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
// ....
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
fakeModal = new FakeModal();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$modal: fakeModal
});
}));
// ....
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
fakeModal.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});