Подтвердить что ты не робот

Как вы издеваетесь над директивами, чтобы включить модульное тестирование директивы более высокого уровня?

В нашем приложении у нас есть несколько уровней вложенных директив. Я пытаюсь написать некоторые модульные тесты для директив верхнего уровня. Я издевался над тем, что сама директива требует, но теперь я сталкиваюсь с ошибками директив нижнего уровня. В моих модульных тестах для директивы верхнего уровня я не хочу беспокоиться о том, что происходит в директивах нижнего уровня. Я просто хочу высмеять директиву нижнего уровня и в основном не делаю ничего, поэтому я могу тестировать директиву верхнего уровня в изоляции.

Я попробовал переписать определение директивы, выполнив что-то вроде этого:

angular.module("myModule").directive("myLowerLevelDirective", function() {
    return {
        link: function(scope, element, attrs) {
            //do nothing
        }
    }
});

Однако это не перезаписывает его, оно просто запускает это в дополнение к реальной директиве. Как я могу остановить эти директивы нижнего уровня от каких-либо действий в моем unit test для директивы верхнего уровня?

4b9b3361

Ответ 1

Из-за реализации регистрации директивы не представляется возможным заменить существующую дирекцию на насмешку.

Однако у вас есть несколько способов unit test вашей директивы более высокого уровня без помех от директив нижнего уровня:

1) Не используйте директиву нижнего уровня в шаблоне unit test:

Если ваша директива более низкого уровня не добавлена ​​директивой более высокого уровня, в unit test используйте шаблон с только директивой более высокого уровня:

var html = "<div my-higher-level-directive></div>";
$compile(html)(scope);

Таким образом, директива нижнего уровня не будет мешать.

2) Используйте службу в своей директивной реализации:

Вы можете обеспечить функцию ссылки на директиву нижнего уровня службой:

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        link: myService.lowerLevelDirectiveLinkingFunction
    }
});

Затем вы можете высмеять эту службу в своем unit test, чтобы избежать вмешательства в вашу директиву более высокого уровня. Эта услуга может даже обеспечить весь объект директивы, если это необходимо.

3) Вы можете перезаписать директиву нижнего уровня с помощью директивы терминала:

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        priority: 100000,
        terminal: true,
        link: function() {
            // do nothing
        }
    }
});

Если опция терминала и более высокий приоритет, ваша реальная директива нижнего уровня не будет выполнена. Дополнительная информация в директивном документе .

Посмотрите, как это работает в этом Plunker.

Ответ 2

Директивы - это просто фабрики, поэтому лучший способ сделать это - издеваться над factory директивы при использовании функции module, как правило, в блоке beforeEach. Предполагая, что у вас есть директива с именем do-something, используемая директивой, называемой do-something-else, вы бы издевались над ней как таковой:

beforeEach(module('yourapp/test', function($provide){
  $provide.factory('doSomethingDirective', function(){ return {}; });
}));

// Or using the shorthand sytax
beforeEach(module('yourapp/test', { doSomethingDirective: {} ));

Тогда директива будет переопределена, когда шаблон будет скомпилирован в вашем тесте

inject(function($compile, $rootScope){
  $compile('<do-something-else></do-something-else>', $rootScope.$new());
});

Обратите внимание, что вам нужно добавить суффикс 'Directive' для имени, потому что компилятор делает это внутри: https://github.com/angular/angular.js/blob/821ed310a75719765448e8b15e3a56f0389107a5/src/ng/compile.js#L530

Ответ 3

Чистым способом насмешки над директивой является $compileProvider

beforeEach(module('plunker', function($compileProvider){
  $compileProvider.directive('d1', function(){ 
    var def = {
      priority: 100,
      terminal: true,
      restrict:'EAC',
      template:'<div class="mock">this is a mock</div>',
    };
    return def;
  });
}));

Вы должны убедиться, что макет получает более высокий приоритет, чем директива, которую вы издеваетесь, и что макет является терминальным, так что исходная директива не будет скомпилирована.

priority: 100,
terminal: true,

Результат будет выглядеть следующим образом:

Учитывая эту директиву:

var app = angular.module('plunker', []);
app.directive('d1', function(){
  var def =  {
    restrict: 'E',
    template:'<div class="d1"> d1 </div>'
  }
  return def;
});

Вы можете издеваться над этим следующим образом:

describe('testing with a mock', function() {
var $scope = null;
var el = null;

beforeEach(module('plunker', function($compileProvider){
  $compileProvider.directive('d1', function(){ 
    var def = {
      priority: 9999,
      terminal: true,
      restrict:'EAC',
      template:'<div class="mock">this is a mock</div>',
    };
    return def;
  });
}));

beforeEach(inject(function($rootScope, $compile) {
  $scope = $rootScope.$new();
  el = $compile('<div><d1></div>')($scope);
}));

it('should contain mocked element', function() {
  expect(el.find('.mock').length).toBe(1);
});
});

Еще несколько вещей:

  • Когда вы создаете свой макет, вам нужно подумать, нужно ли вам replace:true и/или template. Например, если вы издеваетесь ng-src, чтобы предотвратить вызовы на бэкэнд, вам не нужно replace:true, и вы не хотите указывать template. Но если вы издеваетесь над чем-то визуальным, вы можете захотеть.

  • Если вы установите приоритет выше 100, ваши атрибуты mocks не будут интерполированы. См. $компилировать исходный код. Например, если вы издеваетесь ng-src и устанавливаете priority:101, то вы получите ng-src="{{variable}}" not ng-src="interpolated-value" в своем макете.

Вот плункер со всем. Спасибо @trodrigues за то, что указали мне в правильном направлении.

Вот несколько документов, которые объясняют больше, проверьте раздел "Блоки конфигурации". Благодаря @ebelanger!

Ответ 4

Вы можете изменить свои шаблоны внутри $templateCache, чтобы удалить любые директивы нижнего уровня:

beforeEach(angular.mock.inject(function ($templateCache) {
  $templateCache.put('path/to/template.html', '<div></div>');
}));

Ответ 5

Будучи вынужденным думать об этом больше, я придумал решение, которое наполняет наши потребности. Все наши директивы являются атрибутами, поэтому я создал директиву attributeRemover для использования во время модульных тестов. Это выглядит примерно так:

angular.module("myModule").directive("attributeRemover", function() {
    return {
        priority: -1, //make sure this runs last
        compile: function(element, attrs) {
            var attributesToRemove = attrs.attributeRemover.split(",");
            angular.forEach(attributesToRemove, function(currAttributeToRemove) {
                element.find("div[" + currAttributeToRemove + "]").removeAttr(currAttributeToRemove);
            });
        }
    }
});

Затем html для проверяемой директивы выглядит примерно так:

<div my-higher-level-directive attribute-remover="my-lower-level-directive,another-loweler-level-directive"></div>

Итак, когда my-higher-level-directive скомпилируется, attribute-remover уже удалит атрибуты для директив нижнего уровня, и поэтому мне не нужно беспокоиться о том, что они делают.

Вероятно, существует более надежный способ сделать это для всех видов директив (а не только для атрибутов), и я не уверен, что это работает, если использовать только встроенный JQLite, но он работает для того, что нам нужно.

Ответ 6

Loved Sylvain answer настолько, что мне пришлось превратить его в вспомогательную функцию. Чаще всего мне нужно убить дочернюю директиву, чтобы я мог скомпилировать и протестировать директиву родительского контейнера без его зависимостей. Таким образом, этот помощник позволяет нам сделать это:

function killDirective(directiveName) {
  angular.mock.module(function($compileProvider) {
    $compileProvider.directive(directiveName, function() {
      return {
        priority: 9999999,
        terminal: true
      }
    });
  });
}

С этим вы можете полностью отключить директиву, запустив это до создания инжектора:

killDirective('myLowerLevelDirective');

Ответ 7

Вот еще одна небольшая идея. Просто введите этот код в помощники жасмина (кофе script)

window.mockDirective = (name, factoryFunction) ->
  mockModule = angular.module('mocks.directives', ['ng'])
  mockModule.directive(name, factoryFunction)

  module ($provide) ->
    factoryObject = angular.injector([mockModule.name]).get("#{name}Directive")
    $provide.factory "#{name}Directive", -> factoryObject
    null

И используйте его:

beforeEach mockDirective, "myLowerLevelDirective", ->
  link: (scope, element) ->

Это полностью удалит все другие реализации данной директивы, предоставив полный доступ к проверке переданных аргументов директиве. Например, директива mm.foundation alert может быть изделена с помощью:

beforeEach mockDirective 'alert', ->
  scope:
    type: '='

а затем протестирован:

expect(element.find('alert').data('$isolateScopeNoTemplate').type).toEqual