Як одинично перевірити директиву про ізольований обсяг в 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

@Tushar не дуже, як раніше є спосіб змусити його працювати, але він може бути змінений без попередження, тому використовуйте на свій страх і ризик.
daniellmb

Відповіді:


102

Див. Кутовий елемент 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 (jqLite): викрити getlate isolateScope (), схожий на scope () github.com/angular/angular.js/commit/…
daniellmb

1
але де ви шпигуєте за методом $ watch?
tusharmath

1
ви можете виставити функцію, яка працює на годиннику $, а потім шпигувати за нею. В директиві встановіть "scope.myfunc = function () ...", потім у $ watch виконайте "$ scope. $ Watch ('myName', scope.myfunc);". Тепер у тесті ви можете дістати myFunc із ізольованої сфери та підглянути його.
Yair Tavor,

22
Не працює у мене. element.isolateScope()повертається undefined. І element.scope()повертає область, яка не містить усіх речей, які я помістив у свою область.
mcv

4
@mcv Я знайшов, що мені потрібно зробитиelement.children().isolateScope()
Will Keeling

11

Ви можете зробити, var isolateScope = myDirectiveElement.scope()щоб отримати область виділення.

Вам насправді не потрібно перевіряти, що $ watch був викликаний, хоча .. це більше тестування angularjs, ніж тестування вашого додатка. Але, мабуть, це лише приклад запитання.


2
Я не впевнений, що погоджуюсь, що це "тестування angular". Я не тестую, що $ watch працює, а просто те, що директива є властивістю "підключеною" до angular.
daniellmb

1
Також daniellmb, способом перевірити це було б виставити свою greetфункцію та підглянути це, а також перевірити, чи викликається це - не годинник $.
Andrew Joslin

Правильно, це надуманий приклад, але мені було цікаво, чи є чистий спосіб перевірити область ізоляції. Порушення інкапсуляції та введення методів у область дії в цьому випадку не спрацювало б, оскільки немає гачка, щоб додати шпигуна до його виклику.
daniellmb

@AndyJoslin, З цікавості, навіщо isolateScopeвзагалі створювати змінну? Див. Коментар Енга до цього відео з головою яйця ( 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
Небезпека

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, область директиви успадковуватиметься прототипово, і ваші тести повинні пройти.

Думаю, це не найідеальніше рішення, оскільки іноді (багато часу) ізолювати область застосування - це добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.