如何在AngularJs中使用私有方法编写可testing的控制器?
好吧,我长期以来一直在磕磕绊绊,我想听听其他社区的意见。
首先,我们来看看一些抽象控制器。
function Ctrl($scope, anyService) { $scope.field = "field"; $scope.whenClicked = function() { util(); }; function util() { anyService.doSmth(); } }
显然我们在这里:
- 控制器的定期脚手架与
$scope
和一些服务注入 - 一些领域和function附加到范围
- 私有方法
util()
现在,我想在unit testing(Jasmine)中介绍这门课。 但是,问题是我想validation当我点击(调用whenClicked()
)一些项目,将调用util()
方法。 我不知道该怎么做,因为在Jasminetesting中,我总是得到一些错误,要么是util()
的模拟没有被定义或者没有被调用。
注意:我没有试图解决这个特定的例子,我总是在testing这样的代码模式。 所以请不要告诉我“什么是确切的错误”。 我在问怎么做,而不是如何解决这个问题。
我一直在尝试很多方法:
- 显然我不能在我的unit testing中使用
$scope
,因为我没有附加到这个对象的这个函数(它通常以消息Expected spy but got undefined
结尾,Expected spy but got undefined
或相似) - 我尝试通过
Ctrl.util = util;
将这些函数附加到控制器对象Ctrl.util = util;
然后validationCtrl.util = jasmine.createSpy()
像Ctrl.util = jasmine.createSpy()
但在这种情况下Ctrl.util
没有被调用,所以testing失败 - 我试图改变
util()
被附加到this
对象,再次嘲笑Ctrl.util
,没有运气
那么,我无法find解决这个问题的方法,我希望得到JS忍者的一些帮助,一个工作小提琴将是完美的。
命名上的范围是污染。 你想要做的是将该逻辑提取到一个单独的函数,然后注入到你的控制器。 即
function Ctrl($scope, util) { $scope.field = "field"; $scope.whenClicked = function() { util(); }; } angular.module("foo", []) .service("anyService", function(...){...}) .factory("util", function(anyService) { return function() { anyService.doSmth(); }; });
现在,你可以unit testing嘲笑你的Ctrl 以及 “util”。
您提供的控制器函数将被Angular用作构造函数; 在某些时候,它将被调用new
来创build实际的控制器实例。 如果你真的需要在你的控制器对象中没有暴露给$ scope的函数,但是可以用于间谍/存根/模拟,你可以将它们附加到this
。
function Ctrl($scope, anyService) { $scope.field = "field"; $scope.whenClicked = function() { util(); }; this.util = function() { anyService.doSmth(); } }
当您现在调用var ctrl = new Ctrl(...)
或使用Angular $controller
服务来检索Ctrl
实例时,返回的对象将包含util
函数。
你可以在这里看到这个方法: http : //jsfiddle.net/yianisn/8P9Mv/
我要用不同的方法来打招呼。 你不应该testing私有方法。 这就是为什么他们是私人的 – 这是一个实施细节是无关的使用。
例如,如果你意识到util已经在几个地方使用了,但是现在基于其他的代码重构,它只能在这个地方被调用。 为什么要额外的函数调用? 只要在你的$scope.whenClicked()
包含anyService.doSmith()
就上面的build议,假设你正在testingutil()
被调用,即使你没有改变程序的function,你的testing也会中断。 unit testing的主要价值之一是简化重构而不会破坏事物,所以如果你没有破坏东西,testing不应该失败。
你需要做的是确保当$scope.whenClicked
被调用时, anyService.doSmth()
也被调用。 您只需:
spyOn(anyService,'doSmith') scope.whenClicked(); expect(anyService.doSmith).toHaveBeenCalled();
我添加了一个包含我目前的方法的答案,希望得到一些意见,并可能闪耀讨论这是否是一个好的解决scheme。
我们将私人function附加到控制器function(从而使它们公开,这使嘲笑)。 为了避免不得不一直重复控制器的名字,使得语法更具吸引力,我们正在创buildself
对象,这个控制对象具有对控制器function的引用。 所以它变成:
function Ctrl($scope, anyService) { $scope.field = "field"; $scope.whenClicked = function() { self.util(); }; var self = Ctrl; // For the sake of syntax simplicity only self.util = function() { anyService.doSmth(); }; }
然后在unit testing中我们现在可以使用:
Ctrl.util = jasmine.createSpy("util()"); expect(Ctrl.util).toHaveBeenCalled();
我还是不太喜欢这个,但我认为这是做这件事最简单的方法。 我希望有人会find更好的方法。