如何在AngularJs中使用私有方法编写可测试的控制器?

Łuk*_*man 57 testing tdd angularjs

好吧,所以我长期以来一直在绊倒一些问题,我想听听社区其他人的意见.

首先,让我们看一些抽象的控制器.

function Ctrl($scope, anyService) {

   $scope.field = "field";
   $scope.whenClicked = function() {
      util();
   };

   function util() {
      anyService.doSmth();
   }

}
Run Code Online (Sandbox Code Playgroud)

显然我们在这里:

  • 用于控制器的常规脚手架$scope和注入的一些服务
  • 一些字段和功能附加到范围
  • 私人方法 util()

现在,我想在单元测试(Jasmine)中介绍这个课程.但是,问题是我想验证当我单击(调用whenClicked())某个项时util()将调用该方法.我不知道该怎么做,因为在Jasmine测试中我总是得到错误,无论是模拟util()还是没有被调用.

注意:我不是要修复这个特定的例子,我一般都在询问测试这样的代码模式.所以请不要告诉我"究竟是什么错误".我问的是如何做到这一点,而不是如何解决这个问题.

我一直在尝试各种方法:

  • 显然我不能$scope在我的单元测试中使用,因为我没有附加到此对象的此功能(它通常以消息Expected spy but got undefined或类似结尾)
  • 我尝试将这些函数附加到控制器对象Ctrl.util = util;,然后验证模拟,Ctrl.util = jasmine.createSpy()但在这种情况下Ctrl.util没有被调用,因此测试失败
  • 我试图改变util()以附加到this对象并Ctrl.util再次嘲笑,没有运气

好吧,我无法找到解决这个问题的方法,我期待JS忍者的一些帮助,一个工作小提琴将是完美的.

小智 41

您提供的控制器功能将由Angular用作构造函数; 在某些时候,它将被调用new以创建实际的控制器实例.如果您确实需要在控制器对象中具有未暴露于$ scope但可用于间谍/存根/模拟的函数,则可以将它们附加到this.

function Ctrl($scope, anyService) {

  $scope.field = "field";
  $scope.whenClicked = function() {
    util();
  };

  this.util = function() {
    anyService.doSmth();
  }
}
Run Code Online (Sandbox Code Playgroud)

当您现在调用var ctrl = new Ctrl(...)或使用Angular $controller服务来检索Ctrl实例时,返回的对象将包含该util函数.

你可以在这里看到这种方法:http://jsfiddle.net/yianisn/8P9Mv/

  • ......直到ControllerAs出现.怎么办? (15认同)

Mik*_*Mac 31

命名它的范围是污染.你想要做的是将该逻辑提取到一个单独的函数中,然后将其注入到Controller中.即

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();
              };
       });
Run Code Online (Sandbox Code Playgroud)

现在你可以使用模拟你的Ctrl "util" 进行单元测试.

  • @ŁukaszBachman如果您提取到私有方法中的代码太简单而无法将其提取到单独的可模拟服务中,为什么不忽略`Ctrl`的内部代码分区并直接验证此逻辑?即,在您的示例中,不是验证对`util()的调用,而是验证对`anyService.doSmth()`的调用(以及可能在`util()`中的任何其他服务调用). (5认同)

Yeh*_*sef 7

我打算采用不同的方法.你不应该测试私有方法.这就是为什么它们是私有的 - 这是一个与使用无关的实现细节.

例如,如果你意识到在几个地方使用了util,但是现在,根据其他代码重构,它只在这一个地方被调用.为什么有一个额外的函数调用?只是包含anyService.doSmith()在你内部$scope.whenClicked()通过上面的建议,假设您正在测试util()被调用,即使您没有更改程序的功能,您的测试也会中断.单元测试的主要价值之一是简化重构而不会破坏事物,所以如果你没有破坏,测试不应该失败.

您需要做的是确保$scope.whenClicked调用时anyService.doSmth()也称为.您只需要:

spyOn(anyService,'doSmith')
scope.whenClicked();
expect(anyService.doSmith).toHaveBeenCalled();
Run Code Online (Sandbox Code Playgroud)