Angular中的单元测试指令控制器,不会使控制器全局化

Ken*_*nne 66 unit-testing jasmine angularjs angularjs-directive karma-runner

在Vojta Jina的优秀存储库中,他演示了指令的测试,他在模块包装器之外定义了指令控制器.请参见:https: //github.com/vojtajina/ng-directive-testing/blob/master/js/tabs.js

这不是不好的做法并污染全局命名空间吗?

如果一个人有另一个地方可以合理地调用TabsController,那会不会打破这些东西?

所提到的指令的测试可以在这里找到:https://github.com/vojtajina/ng-directive-testing/commit/test-controller

是否可以将指令控制器与指令的其余部分分开测试,而无需将控制器放在全局命名空间中?

将整个指令封装在app.directive(...)定义中会很好.

Jam*_*yke 75

我有时更喜欢将控制器和指令一起包含在内,所以我需要一种方法来测试它.

首先是指令

angular.module('myApp', [])
  .directive('myDirective', function() {
    return {
      restrict: 'EA',
      scope: {},
      controller: function ($scope) {
        $scope.isInitialized = true
      },
      template: '<div>{{isInitialized}}</div>'
    }
})
Run Code Online (Sandbox Code Playgroud)

然后测试:

describe("myDirective", function() {
  var el, scope, controller;

  beforeEach inject(function($compile, $rootScope) {
    # Instantiate directive.
    # gotacha: Controller and link functions will execute.
    el = angular.element("<my-directive></my-directive>")
    $compile(el)($rootScope.$new())
    $rootScope.$digest()

    # Grab controller instance
    controller = el.controller("myDirective")

    # Grab scope. Depends on type of scope.
    # See angular.element documentation.
    scope = el.isolateScope() || el.scope()
  })

  it("should do something to the scope", function() {
    expect(scope.isInitialized).toBeDefined()
  })
})
Run Code Online (Sandbox Code Playgroud)

有关从实例化指令获取数据的更多方法,请参阅angular.element文档.

请注意,实例化该指令意味着控制器和所有链接函数已经运行,因此可能会影响您的测试.

  • 这是一个很好的答案,但需要进行一些小编辑:你需要传入调用指令的名称来调用`el.controller()`.所以在上面的例子中,调用将是`el.controller("myDirective")`. (14认同)
  • 我必须将指令名称传递给Angular 1.3中的控制器方法.没有它,给我"未定义". (6认同)
  • @fiznool是对的,至少根据官方[角度文档](https://docs.angularjs.org/api/ng/function/angular.element#methods).请注意,这不是指令的控制器名称,而是指令名称本身! (4认同)

pko*_*rce 58

好问题!

因此,这是一个常见的问题,不仅与控制器有关,而且可能与指令可能需要执行其工作的服务有关,但不一定要将此控制器/服务暴露给"外部世界".

我坚信全球数据是邪恶的,应该避免,这也适用于指令控制器.如果我们采用这种假设,我们可以采用几种不同的方法来"本地"定义这些控制器.在这样做时我们需要记住,控制器应该仍然可以"轻松"访问单元测试,因此我们不能简单地将其隐藏到指令的闭包中.IMO的可能性是:

1)首先,我们可以简单地在模块级别定义指令的控制器,ex ::

angular.module('ui.bootstrap.tabs', [])
  .controller('TabsController', ['$scope', '$element', function($scope, $element) {
    ...
  }])
 .directive('tabs', function() {
  return {
    restrict: 'EA',
    transclude: true,
    scope: {},
    controller: 'TabsController',
    templateUrl: 'template/tabs/tabs.html',
    replace: true
  };
})
Run Code Online (Sandbox Code Playgroud)

这是我们在https://github.com/angular-ui/bootstrap/blob/master/src/tabs/tabs.js中使用的一种简单技术,它基于Vojta的工作.

虽然这是一种非常简单的技术,但应该注意控制器仍然暴露给整个应用程序,这意味着其他模块可能会覆盖它.从这个意义上说,它使一个控制器成为AngularJS应用程序的本地控制器(因此不会污染全局窗口范围),但它也是所有AngularJS模块的全局控制器.

2)使用闭包范围和特殊文件设置进行测试.

如果我们想完全隐藏控制器函数,我们可以将代码包装在一个闭包中.这是AngularJS正在使用的一种技术.例如,查看NgModelController,我们可以看到它在自己的文件中定义为"全局"函数(因此可以方便地进行测试),但整个文件在构建期间被包装在闭包中:

总结一下:选项(2)"更安全",但需要对构建进行一些预先设置.


小智 9

詹姆斯的方法适合我.但是,当你有一个外部模板时,你必须在$ rootScope.$ digest()之前调用$ httpBackend.flush()才能让angular执行你的控制器.

我想这应该不是问题,如果你使用https://github.com/karma-runner/karma-ng-html2js-preprocessor