【问题标题】:How to Unit Test Isolated Scope Directive in AngularJS如何在 AngularJS 中对隔离范围指令进行单元测试
【发布时间】:2013-06-26 15:32:51
【问题描述】:

在 AngularJS 中对隔离范围进行单元测试的好方法是什么

JSFiddle showing unit test

指令sn-p

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

【问题讨论】:

  • 你找到设置间谍的方法了吗?
  • @Tushar 不是真的,因为以前有办法让它工作,但它可能会在没有通知的情况下更改,因此使用风险自负。

标签: javascript unit-testing angularjs jasmine angularjs-directive


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 我不确定我是否同意它是“测试角度”我不是在测试 $watch 是否有效,而只是该指令是属性“连接”到角度。
    • 还有 daniellmb,测试这个的方法是公开你的 greet 函数并监视它,并检查它是否被调用 - 而不是 $watch。
    • 对,这是一个人为的例子,但如果有一种干净的方法来测试隔离范围,我很感兴趣。在这种情况下,打破封装并将方法放在作用域上是行不通的,因为在调用它之前没有添加 spy 的钩子。
    • @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
    【解决方案3】:

    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() 你会得到 ​​p>

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

    对于 angular 1.2.2 或 1.2.3 来说,这是正确的,但不确定。 在以前的版本中,您只有 element.scope()。

    【讨论】:

    • v1.2.3 feat(jqLit​​e):暴露类似于 scope() github.com/angular/angular.js/commit/…的isolateScope() getter
    • 但是你在哪里监视 $watch 方法?
    • 你可以暴露在 $watch 上运行的函数然后监视它。在指令中,设置“scope.myfunc = function()...”,然后在 $watch 中执行“$scope.$watch('myName', scope.myfunc);”。现在在测试中,您可以从隔离范围中获取 myFunc 并对其进行监视。
    • 对我不起作用。 element.isolateScope() 返回undefinedelement.scope() 返回的范围不包含我放在范围中的所有内容。
    • @mcv 我发现我需要做element.children().isolateScope()
    【解决方案4】:

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

    //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 section here 示例)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多