Javascript 如何在AngularJS中对隔离范围指令进行单元测试

Javascript 如何在AngularJS中对隔离范围指令进行单元测试,javascript,unit-testing,angularjs,jasmine,angularjs-directive,Javascript,Unit Testing,Angularjs,Jasmine,Angularjs Directive,在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

在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));
    });
更新: 我通过检查是否将预期的观察者添加到子作用域中来实现它,但是它非常脆弱,并且可能以未记录的方式使用访问器(也就是说,可能会在不通知的情况下进行更改!)

更新2: 正如我提到的,这是易碎的!这个想法仍然有效,但在AngularJS的较新版本中,访问器已从
scope()
更改为
isolateScope()


我不确定隔离范围是否可行(尽管我希望有人能证明我错了)。指令中创建的隔离作用域是隔离的,因此指令中的$watch方法与单元测试中监视的作用域不同。如果将scope:{}更改为scope:true,则指令作用域将继承原型,并且测试应该通过


我想这不是最理想的解决方案,因为有时候(很多时候),隔离范围是一件好事

您可以执行
var isolateScope=myDirectiveElement.scope()
来获取隔离范围

不过,你真的不需要测试$watch的调用。。这更多的是测试angularjs,而不是测试你的应用程序。但我想这只是这个问题的一个例子。

参见。如果使用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()。

将逻辑移动到单独的控制器,即:

//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);
并像测试任何控制器一样测试后者-直接传入scope对象(请参见控制器以获取示例)

如果您需要在测试中触发$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
  });
});

我不确定我是否同意这是“测试angular”,我不是在测试$watch是否有效,只是测试该指令是否是angular的“连接”属性。还有daniellmb,测试方法是公开你的
问候
函数并监视它,然后检查它是否被调用-而不是$watch。对,这是一个人为的例子,但我感兴趣的是,是否有一种干净的方法来测试隔离范围。在这种情况下,破坏封装并将方法放在作用域上是行不通的,因为在调用spy之前没有钩子添加它。@AndyJoslin,出于好奇,为什么要创建一个
isolateScope
变量呢?参见Ang对这个egghead视频()的评论:从Angular 1.2开始,要检索隔离作用域,需要使用
element.isolateScope()
而不是
element.scope()
v1.2.3壮举(jqLite):与scope()类似的expose isolateScope()getter但是你在哪里监视$watch方法呢?你可以公开在$watch上运行的函数,然后监视它。在指令中,设置“scope.myfunc=function()…”,然后在$watch中设置“$scope.watch('myName',scope.myfunc);”。现在在测试中,你可以从隔离的作用域中获取myFunc并监视它。这对我不起作用
element.isolateScope()
返回
未定义的
。而
element.scope()
返回的作用域不包含我放在作用域上的所有内容。@mcv我发现我需要执行
element.children().isolateScope()
你找到设置间谍的方法了吗?@Tushar不是真的,像以前一样,有一种方法可以让它工作,但它可能会在不通知的情况下更改,因此使用风险自负。
//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);
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
  });
});