В моем последнем блоге мы рассмотрели основы настройки интеграционного тестирования для директивы Angular, которая использует свойство bindToController. Как вы помните, мы начали с простой директивы:

и заканчивался следующим блоком кода для нашего тестового файла:

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

Так давайте это исправим! Если мы отступим назад и посмотрим на то, что у нас есть на данный момент, мы заметим, что наша функция разделена на две части: одна для упаковки переменных области видимости в строку элемента, а другая — для создания области видимости и контроллера, которые нам понадобятся для тестирования.

Начнем с переменных области видимости.

Рефакторинг значений по умолчанию

Поскольку нам, вероятно, потребуется настроить некоторые переменные области видимости в наших тестах, давайте проясним, что исходные переменные области видимости — это просто значения по умолчанию, переименовав их и переместив из beforeEach:

Мы убрали конструкцию по умолчанию из beforeEach по двум причинам: во-первых, она укорачивает функцию beforeEach и облегчает сосредоточение внимания на ее сути. Во-вторых, он делает значения по умолчанию доступными для всех наших тестов на случай, если они понадобятся для сравнения или как способ более быстрой настройки переменных с похожей структурой. К следующей части!

Упаковка шага компиляции

Решение, позволяющее обеспечить гибкость переменных области видимости, состоит в том, чтобы просто запустить этап компиляции внутри каждого из наших тестов. Поскольку у нас нет доступа к функциям $rootScope и $compile за пределами цикла beforeEach, мы можем обернуть этап компиляции в функцию, назначить ее глобальной области видимости, а затем вызвать функцию из наших тестов:

//The new function.
buildDirective = function(){
    element = angular.element(defaultElementString);
    element = $compile(element)(scope);
    scope.$digest();
    controller = element.controller('fancySelect');
}
//An example test. Obviously not the most useful of tests.
it ("should initialize someVar as true", inject(function(){
    buildDirective(); 
    expect (controller.someVar).toBe(true);
}))

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

buildDirective = function(elementString){
    if (angular.isUndefined(elementString))
        elementString = defaultElementString;
    element = angular.element(elementString);
    element = $compile(element)(scope);
    scope.$digest();
    controller = element.controller('searchSelect');
}

Наш последний тестовый файл:

Прежде чем мы закончим, давайте взглянем на несколько разных простых тестовых случаев. Это даст нам лучшее представление о том, как использовать нашу функцию buildDirective(), а также продемонстрирует некоторые общие тестовые структуры.

Тестирование с нестандартными переменными области видимости:

Просто установите свои собственные переменные области видимости перед вызовом функции buildDirective, и все готово.

Тестирование исключения:

Единственное примечательное отличие состоит в том, что действие, вызывающее исключение, должно вызываться как часть expect в сочетании с .toThrowError().

Тестирование с имитацией событий:

Для имитации событий мы можем воспользоваться очень удобной функцией .triggerHandler(). Обратите внимание, что angular поставляется только с jqLite, поэтому вам придется включить полную версию jQuery, если вы хотите использовать .find() с идентификаторами элементов (или любые другие функции jQuery, не включенные в jqLite).

Вот пример запуска событий «фокус» и «размытие»:

Тестирование с пользовательскими данными о событиях:

Для любого события, требующего дополнительных данных (например, кода клавиши), нам нужно передать объект в функцию .triggerHandler():

Обратите внимание, что для того, чтобы это работало, необходима клавиша тип. Оболочка Angular для функции .triggerHandler() использует наличие ключа type в качестве флага для слияния переданных данных события с фиктивным событием, которое создает .triggerHandler(). Для полного объяснения этого взаимодействия вы можете перейти по этой ссылке.

Верхушка айсберга

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

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

Спасибо за прочтение, надеюсь, вы будете с нетерпением ждать моего следующего блога!