This is a somewhat of a follow-on question to this one: Mocking $modal in AngularJS unit tests
对于这个问题,这是一个后续问题:在AngularJS单元测试中模拟$ modal
The referenced SO is an excellent question with very useful answer. The question I am left with after this however is this: how do I unit test the modal instance controller? In the referenced SO, the invoking controller is tested, but the modal instance controller is mocked. Arguably the latter should also be tested, but this has proven to be very tricky. Here's why:
引用的SO是一个非常有用的答案的优秀问题。我之后留下的问题是:我如何对模态实例控制器进行单元测试?在引用的SO中,测试调用控制器,但模拟了模态实例控制器。可以说后者也应该进行测试,但事实证明这非常棘手。原因如下:
I'll copy the same example from the referenced SO here:
我将在这里复制引用的SO中的相同示例:
.controller('ModalInstanceCtrl', function($scope, $modalInstance, items){
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
So my first thought was that I would just instantiate the controller directly in a test, just like any other controller under test:
所以我的第一个想法是,我只是在测试中直接实例化控制器,就像测试中的任何其他控制器一样:
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
ctrl = $controller('ModalInstanceCtrl', {$scope: scope});
});
This does not work because in this context, angular does not have a provider to inject $modalInstance, since that is supplied by the UI modal.
这不起作用,因为在这种情况下,angular没有提供者注入$ modalInstance,因为它是由UI模式提供的。
Next, I turn to plan B: use $modal.open to instantiate the controller. This will run as expected:
接下来,我转向计划B:使用$ modal.open来实例化控制器。这将按预期运行:
beforeEach(inject(function($rootScope, $modal) {
scope = $rootScope.$new();
modalInstance = $modal.open({
template: '<html></html>',
controller: 'ModalInstanceCtrl',
scope: scope
});
});
(Note, template can't be an empty string or it will fail cryptically.)
(注意,模板不能是空字符串,否则会以密码方式失败。)
The problem now is that I have no visibility into the scope, which is the customary way to unit test resource gathering, etc. In my real code, the controller calls a resource service to populate a list of choices; my attempt to test this sets an expectGet to satisfy the service my controller is using, and I want to validate that the controller is putting the result in its scope. But the modal is creating a new scope for the modal instance controller (using the scope I pass in as a prototype), and I can't figure out how I can get a hole of that scope. The modalInstance object does not have a window into the controller.
现在的问题是我无法看到范围,这是单元测试资源收集的习惯方式等。在我的实际代码中,控制器调用资源服务来填充选择列表;我尝试测试这个设置expectGet以满足我的控制器正在使用的服务,我想验证控制器是否将结果放在其范围内。但模式是为模态实例控制器创建一个新的范围(使用我作为原型传入的范围),我无法弄清楚如何获得该范围的漏洞。 modalInstance对象没有进入控制器的窗口。
Any suggestions on the "right" way to test this?
有关“正确”测试方法的任何建议吗?
(N.B.: the behavior of creating a derivative scope for the modal instance controller is not unexpected – it is documented behavior. My question of how to test it is still valid regardless.)
(N.B。:为模态实例控制器创建衍生作用域的行为并不是意料之外的 - 它是记录在案的行为。无论如何,我对如何测试它的问题仍然有效。)
4 个解决方案
#1
28
I test the controllers used in modal dialogs by instantiating the controller directly (the same way you initially thought to do it above).
我通过直接实例化控制器来测试模态对话框中使用的控制器(就像你最初想的那样)。
Since there there's no mocked version of $modalInstance
, I simply create a mock object and pass that into the controller.
由于没有模拟版本的$ modalInstance,我只需创建一个模拟对象并将其传递给控制器。
var modalInstance = { close: function() {}, dismiss: function() {} };
var items = []; // whatever...
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
ctrl = $controller('ModalInstanceCtrl', {
$scope: scope,
$modalInstance: modalInstance,
items: items
});
}));
Now the dependencies for the controller are satisfied and you can test this controller like any other controller.
现在,控制器的依赖性得到满足,您可以像任何其他控制器一样测试此控制器。
For example, I can do spyOn(modalInstance, 'close')
and then assert that my controller is closing the dialog at the appropriate time.
例如,我可以执行spyOn(modalInstance,'close')然后断言我的控制器在适当的时候关闭对话框。
#2
13
Alternatively, if you're using jasmine, you can mock the $uibModalInstance
using the createSpy
method:
或者,如果您使用的是jasmine,则可以使用createSpy方法模拟$ uibModalInstance:
beforeEach(inject(function ($controller, $rootScope) {
$scope = $rootScope.$new();
$uibModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
ModalCtrl = $controller('ModalCtrl', {
$scope: $scope,
$uibModalInstance: $uibModalInstance,
});
}));
And test it without having to call spyOn
on each method, let's say you have 2 scope methods, cancel()
and confirm()
:
并且无需在每个方法上调用spyOn来测试它,假设你有两个范围方法,cancel()和confirm():
it('should let the user dismiss the modal', function () {
expect($scope.cancel).toBeDefined();
$scope.cancel();
expect($uibModalInstance.dismiss).toHaveBeenCalled();
});
it('should let the user confirm the modal', function () {
expect($scope.confirm).toBeDefined();
$scope.confirm();
expect($uibModalInstance.close).toHaveBeenCalled();
});
#3
0
The same problem is with $uidModalInstance and you can solve it in similar way:
同样的问题是$ uidModalInstance,你可以用类似的方式解决它:
var uidModalInstance = { close: function() {}, dismiss: function() {} };
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
or as said @yvesmancera you can use jasmine.createSpy method instead, like:
或者像@yvesmancera那样你可以使用jasmine.createSpy方法,比如:
var uidModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
#4
0
Follow below given steps:
按照以下给出的步骤:
-
Define stub for ModalInstance like give below
为ModalInstance定义存根,如下所示
uibModalInstanceStub = { close: sinon.stub(), dismiss: sinon.stub() };
-
Pass the modal instance stub while creating controller
在创建控制器时传递模态实例存根
function createController() { return $controller( ppcConfirmGapModalComponentFullName, { $scope: scopeStub, $uibModalInstance: uibModalInstanceStub }); } });
-
Stub methods close(), dismiss() will get called as part of the tests
Stub方法close(),dismiss()将作为测试的一部分被调用
it('confirm modal - verify confirm action, on ok() call calls modalInstance close() function', function() { action = 'Ok'; scopeStub.item = testItem; createController(); scopeStub.ok(); });
it('确认模态 - 验证确认操作,关于ok()调用调用modalInstance close()function',function(){action ='Ok'; scopeStub.item = testItem; createController(); scopeStub.ok();} );
#1
28
I test the controllers used in modal dialogs by instantiating the controller directly (the same way you initially thought to do it above).
我通过直接实例化控制器来测试模态对话框中使用的控制器(就像你最初想的那样)。
Since there there's no mocked version of $modalInstance
, I simply create a mock object and pass that into the controller.
由于没有模拟版本的$ modalInstance,我只需创建一个模拟对象并将其传递给控制器。
var modalInstance = { close: function() {}, dismiss: function() {} };
var items = []; // whatever...
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
ctrl = $controller('ModalInstanceCtrl', {
$scope: scope,
$modalInstance: modalInstance,
items: items
});
}));
Now the dependencies for the controller are satisfied and you can test this controller like any other controller.
现在,控制器的依赖性得到满足,您可以像任何其他控制器一样测试此控制器。
For example, I can do spyOn(modalInstance, 'close')
and then assert that my controller is closing the dialog at the appropriate time.
例如,我可以执行spyOn(modalInstance,'close')然后断言我的控制器在适当的时候关闭对话框。
#2
13
Alternatively, if you're using jasmine, you can mock the $uibModalInstance
using the createSpy
method:
或者,如果您使用的是jasmine,则可以使用createSpy方法模拟$ uibModalInstance:
beforeEach(inject(function ($controller, $rootScope) {
$scope = $rootScope.$new();
$uibModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
ModalCtrl = $controller('ModalCtrl', {
$scope: $scope,
$uibModalInstance: $uibModalInstance,
});
}));
And test it without having to call spyOn
on each method, let's say you have 2 scope methods, cancel()
and confirm()
:
并且无需在每个方法上调用spyOn来测试它,假设你有两个范围方法,cancel()和confirm():
it('should let the user dismiss the modal', function () {
expect($scope.cancel).toBeDefined();
$scope.cancel();
expect($uibModalInstance.dismiss).toHaveBeenCalled();
});
it('should let the user confirm the modal', function () {
expect($scope.confirm).toBeDefined();
$scope.confirm();
expect($uibModalInstance.close).toHaveBeenCalled();
});
#3
0
The same problem is with $uidModalInstance and you can solve it in similar way:
同样的问题是$ uidModalInstance,你可以用类似的方式解决它:
var uidModalInstance = { close: function() {}, dismiss: function() {} };
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
or as said @yvesmancera you can use jasmine.createSpy method instead, like:
或者像@yvesmancera那样你可以使用jasmine.createSpy方法,比如:
var uidModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
#4
0
Follow below given steps:
按照以下给出的步骤:
-
Define stub for ModalInstance like give below
为ModalInstance定义存根,如下所示
uibModalInstanceStub = { close: sinon.stub(), dismiss: sinon.stub() };
-
Pass the modal instance stub while creating controller
在创建控制器时传递模态实例存根
function createController() { return $controller( ppcConfirmGapModalComponentFullName, { $scope: scopeStub, $uibModalInstance: uibModalInstanceStub }); } });
-
Stub methods close(), dismiss() will get called as part of the tests
Stub方法close(),dismiss()将作为测试的一部分被调用
it('confirm modal - verify confirm action, on ok() call calls modalInstance close() function', function() { action = 'Ok'; scopeStub.item = testItem; createController(); scopeStub.ok(); });
it('确认模态 - 验证确认操作,关于ok()调用调用modalInstance close()function',function(){action ='Ok'; scopeStub.item = testItem; createController(); scopeStub.ok();} );