AngularJSで分離スコープディレクティブを単体テストする方法


81

AngularJSで分離スコープを単体テストするための良い方法は何ですか

ユニットテストを示すJSFiddle

ディレクティブスニペット

    scope: {name: '=myGreet'},
    link: function (scope, element, attrs) {
        //show the initial state
        greet(element, scope[attrs.myGreet]);

        //listen for changes in the model
        scope.$watch(attrs.myGreet, function (name) {
            greet(element, name);
        });
    }

ディレクティブが変更をリッスンしていることを確認したい-これは分離されたスコープで機能しませ

    it('should watch for changes in the model', function () {
        var elm;
        //arrange
        spyOn(scope, '$watch');
        //act
        elm = compile(validHTML)(scope);
        //assert
        expect(scope.$watch.callCount).toBe(1);
        expect(scope.$watch).toHaveBeenCalledWith('name', jasmine.any(Function));
    });

更新: 予想されるウォッチャーが子スコープに追加されているかどうかを確認することで機能しましたが、非常に脆弱で、おそらく文書化されていない方法でアクセサーを使用しています(別名、予告なしに変更される可能性があります!)。

//this is super brittle, is there a better way!?
elm = compile(validHTML)(scope);
expect(elm.scope().$$watchers[0].exp).toBe('name');

更新2: 私が言ったように、これはもろいです!このアイデアは引き続き機能しますが、AngularJSの新しいバージョンでは、アクセサーがからscope()に変更されていisolateScope()ます。

//this is STILL super brittle, is there a better way!?
elm = compile(validHTML)(scope);                       
expect(elm.isolateScope().$$watchers[0].exp).toBe('name');

スパイを設定する方法を見つけましたか?
tusharmath 2014

@Tusharは実際にはそうではありません。以前のように機能させる方法はありますが、予告なしに変更される可能性があるため、自己責任で使用してください。
daniellmb 2014

回答:


102

角度要素のAPIドキュメントを参照してください。element.scope()を使用すると、ディレクティブのscopeプロパティで定義した要素のスコープを取得できます。element.isolateScope()を使用すると、分離されたスコープ全体を取得できます。たとえば、ディレクティブが次のようになっている場合:

scope : {
 myScopeThingy : '='
},
controller : function($scope){
 $scope.myIsolatedThingy = 'some value';
}

次に、テストでelement.scope()を呼び出すと、

{ myScopeThingy : 'whatever value this is bound to' }

ただし、element.isolateScope()を呼び出すと、次のようになります。

{ 
  myScopeThingy : 'whatever value this is bound to', 
  myIsolatedThingy : 'some value'
}

これは、Angular 1.2.2または1.2.3の時点で当てはまりますが、正確にはわかりません。以前のバージョンでは、element.scope()しかありませんでした。


1
v1.2.3偉業(jqLit​​e):isolateScope()ゲッタースコープに類似()露出github.com/angular/angular.js/commit/...
daniellmb

1
しかし、$ watchメソッドをどこでスパイしますか?
tusharmath 2014

1
$ watchで実行される関数を公開して、それをスパイすることができます。ディレクティブで「scope.myfunc = function()...」を設定し、次に$ watchで「$ scope。$ watch( 'myName'、scope.myfunc);」を実行します。これで、テストで、分離されたスコープからmyFuncを取得し、それをスパイすることができます。
Yair

22
私にはうまくいきません。element.isolateScope()を返しますundefined。そしてelement.scope()、スコープに置いたものがすべて含まれていないスコープを返します。
mcv 2014

4
@mcv私がする必要があるとわかったelement.children().isolateScope()
Will Keeling

11

var isolateScope = myDirectiveElement.scope()分離スコープを取得するために行うことができます。

ただし、$ watchが呼び出されたことをテストする必要はありません。これは、アプリのテストよりもangularjsのテストの方が多くなります。しかし、それは質問の単なる例だと思います。


2
「Angularのテスト」であることに同意するかどうかはわかりません。$ watchが機能することをテストしているのではなく、単にディレクティブがAngularに「ワイヤードアップ」されていることをテストしています。
daniellmb 2013年

1
また、daniellmb、これをテストする方法は、greet関数を公​​開してそれをスパイし、それが呼び出されているかどうかを確認することです-$ watchではありません。
Andrew Joslin 2013

そうです、これは不自然な例ですが、分離スコープをテストするためのクリーンな方法があるかどうかに興味がありました。この場合、呼び出される前にスパイを追加するフックがないため、カプセル化を解除してスコープにメソッドを配置することはできません。
daniellmb 2013

@AndyJoslin、好奇心から、なぜisolateScope変数を作成するのですか?このeggheadビデオに関するAngのコメントを参照してください(egghead.io/lessons/angularjs-unit-testing-directive-scope):Angular 1.2以降、分離されたスコープを取得するelement.isolateScope()には、element.scope() code.angularjs.org /1.2の
Danger14

1

ロジックを別のコントローラーに移動します。

//will get your isolate scope
function MyCtrl($scope)
{
  //non-DOM manipulating ctrl logic here
}
app.controller(MyCtrl);

function MyDirective()
{
  return {
    scope     : {},
    controller: MyCtrl,
    link      : function (scope, element, attrs)
    {
      //moved non-DOM manipulating logic to ctrl
    }
  }
}
app.directive('myDirective', MyDirective);

他のコントローラーと同じように後者をテストします-スコープオブジェクトを直接渡します(例については、ここのコントローラーの セクションを参照してください)。

テストで$ watchをトリガーする必要がある場合は、次のようにします。

describe('MyCtrl test', function ()
{
  var $rootScope, $controller, $scope;

  beforeEach(function ()
  {
    inject(function (_$rootScope_, _$controller_)
    {
      // The injector unwraps the underscores (_) from around the parameter names when matching
      $rootScope = _$rootScope_;
      $controller = _$controller_;
    });

    $scope = $rootScope.$new({});
    $scope.foo = {x: 1}; //initial scope state as desired
    $controller(MyCtrl, {$scope: $scope}); //or by name as 'MyCtrl'
  });

  it('test scope property altered on $digest', function ()
  {
    $scope.$digest(); //trigger $watch
    expect($scope.foo.x).toEqual(1); //or whatever
  });
});

0

分離スコープでそれが可能かどうかはわかりません(誰かが私を間違っていると証明してくれることを願っていますが)。ディレクティブで作成される分離スコープは分離されているため、ディレクティブの$ watchメソッドは、単体テストでスパイしているスコープとは異なります。scope:{}をscope:trueに変更すると、ディレクティブスコープはプロトタイプで継承され、テストに合格するはずです。

これは最も理想的な解決策ではないと思います。なぜなら、時々(多くの場合)、スコープを分離するのは良いことだからです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.