web-dev-qa-db-ja.com

Angularでコントローラをグローバルにせずに単体テストディレクティブコントローラ

ディレクティブのテストを示す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(...)定義内にディレクティブ全体をカプセル化することは素晴らしいことです。

66
Kenneth Lynne

素晴らしい質問です!

そのため、これはコントローラーだけでなく、ディレクティブがそのジョブを実行する必要があるかもしれないが、必ずしもこのコントローラー/サービスを「外部世界」に公開したくないサービスに関しても共通の懸念事項です。

グローバルデータは悪であり、避けるべきだと強く信じています。これはディレクティブコントローラにも当てはまります。この仮定を採用すると、いくつかの異なるアプローチを使用して、これらのコントローラーを「ローカル」に定義できます。その間、コントローラーはユニットテストに「簡単に」アクセスできる必要があることに注意する必要があります。それを単純にディレクティブに隠すことはできません。閉鎖。 IMOの可能性は次のとおりです。

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

これは、Vojtaの作業に基づく https://github.com/angular-ui/bootstrap/blob/master/src/tabs/tabs.js で使用している単純な手法です。

これは非常に単純な手法ですが、コントローラーは依然としてアプリケーション全体に公開されているため、他のモジュールがコントローラーをオーバーライドする可能性があることに注意してください。この意味では、AngularJSアプリケーションに対してローカルなコントローラーを作成します(したがって、グローバルウィンドウスコープを汚染しません)が、すべてのAngularJSモジュールに対してもグローバルにします。

2)テストにはクロージャスコープと特別なファイルセットアップを使用します

コントローラー関数を完全に隠したい場合は、クロージャーでコードをラップできます。これは、AngularJSが使用している手法です。たとえば、 NgModelController を見ると、独自のファイルで「グローバル」関数として定義されているため(テスト用に簡単にアクセスできます)、ビルド中にファイル全体がクロージャーでラップされていることがわかります。時間:

要約すると、オプション(2)は「より安全」ですが、ビルドには少し事前のセットアップが必要です。

58

私は時々、コントローラをディレクティブとともに含めることを好むので、それをテストする方法が必要です。

まず指令

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 documentation を参照してください。

ディレクティブをインスタンス化することは、コントローラーとすべてのリンク機能が既に実行されていることを意味するため、テストに影響する可能性があることに注意してください。

75
James van Dyke

ジェームズの方法は私には有効です。ただし、外部テンプレートを使用する場合は、コントローラーを実行するために、$ rootScope。$ digest()の前に$ httpBackend.flush()を呼び出して、angularを実行する必要があります。

https://github.com/karma-runner/karma-ng-html2js-preprocessor を使用している場合、これは問題になりません。

9
buddyspike28

この方法で何か問題がありますか?コントローラをグローバルネームスペースに配置することを避け、不要な$ compiling 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');
4
jbmilgrom

IIFEを使用します。これは、グローバルな名前空間の競合を回避するための一般的な手法です。また、トリッキーなインライン体操を節約し、さらにスコープの自由度を高めます。

 (function(){

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

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

  function MyDirectiveController ($scope) {

  }

})();
0