如何在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年

Answers:


102

请参阅angular element 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'
}

从角度1.2.2或1.2.3开始,这是正确的,但不确定。在以前的版本中,您只有element.scope()。


1
v1.2.3 feat(jqLit​​e):暴露与scope()类似的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 Tavor 2014年

22
对我不起作用。element.isolateScope()返回undefined。并element.scope()返回一个不包含我放在示波器上的所有内容的示波器。
mcv

4
@mcv我发现我需要做element.children().isolateScope()
Will Keeling

11

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

您实际上并不需要测试$ watch是否被调用..这比测试您的应用程序测试的角度更多。但是我想这只是一个例子。


2
我不确定我是否同意它是“测试角度的”,我不是在测试$ watch是否有效,而只是测试指令是将属性“连接”到角度。
daniellmb

1
也是daniellmb,测试此方法的方法是公开您的greet函数并对其进行监视,并检查是否调用了该函数-而不是$ watch。
安德鲁·乔斯林

是的,这是一个人为的示例,但是我对是否有一种测试隔离范围的干净方法很感兴趣。在这种情况下,破坏封装并将方法放到作用域上是行不通的,因为在调用之前没有钩子可以添加间谍。
daniellmb

@AndyJoslin,出于好奇,为什么还要创建一个isolateScope变量?请参阅Ang对这个蛋头视频的评论(egghead.io/lessons/angularjs-unit-testing-directive-scope):从Angular 1.2开始,要检索隔离的作用域,需要使用element.isolateScope()而不是element.scope() code.angularjs.org/1.2。 0 / docs / api / angular.element
Danger14,2014年

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方法不同于您在单元测试中监视的范围。如果将范围:{}更改为范围:true,则指令范围将原型继承,并且您的测试将通过。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.