如何在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更好的方法。