AngularJSで孤立したスコープを単体テストする良い方法は何ですか
ディレクティブスニペット
_ 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);
});
}
_
ディレクティブが変更をリッスンしていることを確認したい-これは、孤立したスコープでnot動作します:
_ 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));
});
_
UPDATE:期待されるウォッチャーが子スコープに追加されたかどうかを確認することで動作するようになりましたが、非常に壊れやすく、おそらく文書化されていないでアクセサーを使用します方法(別名、予告なしに変更される場合があります!)。
_//this is super brittle, is there a better way!?
Elm = compile(validHTML)(scope);
expect(Elm.scope().$$watchers[0].exp).toBe('name');
_
UPDATE 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');
_
angular element api docs を参照してください。 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()のみでした。
var isolateScope = myDirectiveElement.scope()
を実行して、分離スコープを取得できます。
ただし、$ watchが呼び出されたことをテストする必要はありません。これは、アプリをテストするよりも、angularjsをテストすることです。しかし、それは質問の一例に過ぎないと思います。
ロジックを別のコントローラーに移動します。つまり:
//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);
コントローラーと同様に後者をテストします-スコープオブジェクトを直接渡します(Controllersセクションはこちら を参照してください)。
テストで$ 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
});
});
分離スコープでそれが可能かどうかはわかりません(誰かが間違っていることを証明してくれるといいのですが)。ディレクティブで作成される分離スコープは分離されているため、ディレクティブの$ watchメソッドは、単体テストでスパイしているスコープとは異なります。 scope:{}をscope:trueに変更すると、ディレクティブスコープはプロトタイプを継承し、テストに合格するはずです。
私はこれが最も理想的な解決策ではないと思います。なぜなら、(多くの場合)分離スコープは良いことだからです。