66

在 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(...) 定义中会很好。

4

5 回答 5

75

我有时更喜欢在指令中包含我的控制器,所以我需要一种方法来测试它。

首先是指令

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

然后是测试:

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()
  })
})

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

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

于 2014-07-15T14:41:22.273 回答
58

好问题!

因此,这是一个普遍的问题,不仅与控制器有关,而且可能与指令可能需要执行其工作但不一定希望将此控制器/服务公开给“外部世界”的服务有关。

我坚信全局数据是邪恶的,应该避免,这也适用于指令控制器。如果我们采用这个假设,我们可以采用几种不同的方法来“本地”定义这些控制器。在这样做的同时,我们需要记住,控制器应该仍然“容易”被单元测试访问,所以我们不能简单地将它隐藏到指令的闭包中。国际海事组织的可能性是:

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

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
  };
})

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

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

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

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

总而言之:选项(2)“更安全”,但需要一些前期设置来构建。

于 2013-03-09T19:22:59.600 回答
10

詹姆斯的方法对我有用。但是,一个小转折是,当您有一个外部模板时,您必须在 $rootScope.$digest() 之前调用 $httpBackend.flush() 才能让 Angular 执行您的控制器。

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

于 2015-02-18T07:28:42.140 回答
4

这样做有什么问题吗?似乎更可取,因为您避免将控制器放置在全局名称空间中,并且能够测试您想要的(即控制器)而无需不必要地编译 html。

示例指令定义:

 .directive('tabs', function() {
  return {
    restrict: 'EA',
    transclude: true,
    scope: {},
    controller: function($scope, $attrs) {
      this.someExposedMethod = function() {};
    },
    templateUrl: 'template/tabs/tabs.html',
    replace: true
  };

然后在你的 Jasmine 测试中,询问你使用“name + Directive”(例如“tabsDirective”)创建的指令:

var tabsDirective = $injector.get('tabsDirective')[0];
// instantiate and override locals with mocked test data
var tabsDirectiveController = $injector.instantiate(tabsDirective.controller, {
  $scope: {...}
  $attrs: {...}
});

现在您可以测试控制器方法:

expect(typeof tabsDirectiveController.someExposedMethod).toBe('function');
于 2016-08-05T11:39:19.753 回答
0

使用 IIFE,这是一种避免全局命名空间冲突的常用技术,它还可以节省棘手的内联体操,并在您的范围内提供自由。

 (function(){

  angular.module('app').directive('myDirective', function(){
     return {
       .............
       controller : MyDirectiveController,
       .............
     }
  });

  MyDirectiveController.$inject = ['$scope'];

  function MyDirectiveController ($scope) {

  }

})();
于 2017-12-08T15:47:09.540 回答