80

單元測試隔離範圍指令什麼是在AngularJS單元測試隔離範圍的好辦法片斷</p> <p><a href="http://jsfiddle.net/daniellmb/b7BRR">JSFiddle showing unit test</a></p> <p>指令如何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); 
     }); 
    } 

我希望確保該指令是聽對於更改 - 這不是不是使用隔離範圍:

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

更新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'); 
+0

你找到一個方法來設置的間諜? – Tushar

+0

@Tushar並不像以前那樣有辦法讓它起作用,但它可能會在沒有通知的情況下發生變化,所以請自擔風險。 – daniellmb

回答

99

參見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' 
} 

對於角1.2.2或1.2.3,這是正確的,不能確切地確定。 在以前的版本中,只有element.scope()。

+1

v1.2.3 feat(jqLit​​e):顯示類似於範圍的isolateScope()getter() )https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b – daniellmb

+1

但你在哪裏監視$ watch方法? – Tushar

+1

你可以暴露在$ watch上運行的函數,然後監視它。在指令中,設置「scope.myfunc = function()...」,然後在$ watch中執行$ scope。$ watch('myName',scope.myfunc);「。現在在測試中,您可以從隔離範圍獲取myFunc並窺探它。 –

0

我不知道這是可能的分離範圍(雖然我希望有人證明我是錯的)。在指令中創建的隔離範圍很好地隔離,因此指令中的$ watch方法與您在單元測試中執行的範圍不同。如果將範圍:{}更改爲範圍:true,則指令範圍將繼承原型,並且您的測試應通過。

我想這不是最理想的解決方案,因爲有時(很多時候),隔離範圍是件好事。

11

你可以做var isolateScope = myDirectiveElement.scope()來獲得隔離範圍。

你並不需要測試$ watch被調用,雖然..這是更多的測試angularjs比測試你的應用程序。但我想這只是這個問題的一個例子。

+2

我不確定我是否同意它是「測試角度」我沒有測試$ watch的作品,而只是指令是屬性「連線」到角度。 – daniellmb

+1

另外daniellmb,測試這種方式將暴露你的'問候'功能和間諜,並檢查是否被稱爲 - 不是$手錶。 –

+0

沒錯,這是一個人爲的例子,但是我對如果有一種乾淨的方法來測試隔離範圍感興趣。在這種情況下打破封裝並將方法放在範圍上將不起作用,因爲在被調用之前沒有掛鉤來添加間諜。 – daniellmb

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

和測試後,你會任何控制器 - 傳遞的範圍對象直接(參見控制器section here的示例)。

,如果你需要觸發$表中的結果如何:

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 
    }); 
}); 
相關問題