Как написать тестируемые контроллеры с частными методами в AngularJs?
хорошо, так что я наткнулся на какой-то вопрос в течение длительного времени, и я хотел бы услышать мнение от остальной части сообщества.
во-первых, давайте посмотрим на какой-то абстрактный контроллер.
function Ctrl($scope, anyService) {
$scope.field = "field";
$scope.whenClicked = function() {
util();
};
function util() {
anyService.doSmth();
}
}
ясно, что мы имеем здесь:
- регулярная ремонтина для регулятора с
$scope
и какой-то сервис ввели - некоторые поля и функции, прикрепленные к области
- частный метод
util()
теперь я хотел бы осветить этот класс в модульных тестах (Jasmine). Однако проблема в том, что я хочу проверить это, когда я нажимаю (call whenClicked()
) какой-то предмет, что util()
метод будет вызван. Я не знаю, как это сделать, так как в тестах Jasmine я всегда получаю ошибки, которые либо издеваются над util()
не был определен или не был вызван.
примечание: Я не пытаюсь исправить этот конкретный пример, я спрашиваю о тестировании такой код картина в целом. Поэтому, пожалуйста, не говорите мне "что такое точная ошибка". Я спрашиваю, как это сделать, а не как это исправить.
я пробовал несколько способов обойти это:
- я не могу использовать
$scope
в моих модульных тестах, поскольку у меня нет этой функции, прикрепленной к этому объекту (обычно она заканчивается сообщениемExpected spy but got undefined
или аналогичные) - я попытался прикрепить эти функции к объекту контроллера через
Ctrl.util = util;
а потом проверка издевается какCtrl.util = jasmine.createSpy()
но в этом случаеCtrl.util
не называется так тесты терпят неудачу - я пытался изменить
util()
для приобщения кthis
объект и насмешкаCtrl.util
опять не повезло
Ну, я не могу найти свой путь вокруг этого, я ожидал бы некоторой помощи от JS ninjas, рабочая скрипка была бы идеальной.
4 ответа:
пространство имен в области-это загрязнение. Что вы хотите сделать, это извлечь эту логику в отдельную функцию, которая затем вводится в контроллер. то есть
function Ctrl($scope, util) { $scope.field = "field"; $scope.whenClicked = function() { util(); }; } angular.module("foo", []) .service("anyService", function(...){...}) .factory("util", function(anyService) { return function() { anyService.doSmth(); }; });
теперь вы можете модульный тест с издевается над Ctrl а также "util".
предоставленная вами функция контроллера будет использоваться Angular в качестве конструктора; в какой-то момент она будет вызвана с помощью
new
для создания фактического экземпляра контроллера. Если вам действительно нужны функции в вашем объекте контроллера, которые не подвержены $ scope, но доступны для шпионажа / stubbing/mocking, вы можете прикрепить их кthis
.function Ctrl($scope, anyService) { $scope.field = "field"; $scope.whenClicked = function() { util(); }; this.util = function() { anyService.doSmth(); } }
когда вы сейчас позвоните
var ctrl = new Ctrl(...)
или использовать угловые$controller
сервис для полученияCtrl
экземпляр объекта возвращенный будет содержать
Я собираюсь перезвонить с другим подходом. Вы не должны тестировать частные методы. Вот почему они являются частными - это деталь реализации, которая не имеет значения для использования.
например, что делать, если вы понимаете, что util использовался в нескольких местах, но теперь, основываясь на другом рефакторинге кода, он вызывается только в этом месте. Почему есть дополнительный вызов функции? Просто включите
anyService.doSmith()
внутри$scope.whenClicked()
С предложениями выше, предполагая, что вы тестируете, чтоutil()
называется, ваши тесты сломаются, даже если вы не изменили функциональность программы. Одна из главных ценностей модульного тестирования-упростить рефакторинг, не нарушая вещи, поэтому, если вы не нарушили вещи, тест не должен провалиться.что вам нужно сделать, это убедиться, что при
$scope.whenClicked
называетсяanyService.doSmth()
также называется. Вам просто нужно:spyOn(anyService,'doSmith') scope.whenClicked(); expect(anyService.doSmith).toHaveBeenCalled();
я добавляю ответ, содержащий мой текущий подход, надеясь получить некоторые комментарии и, возможно, искру обсуждения о том, является ли это хорошим решением.
мы прикрепляем частные функции к функции контроллера (тем самым делая их общедоступными, что позволяет издеваться). Чтобы избежать необходимости повторять имя контроллера все время и сделать синтаксис более привлекательным, мы создаем
self
объект, который содержит ссылку на функцию контроллера. Так что становится:function Ctrl($scope, anyService) { $scope.field = "field"; $scope.whenClicked = function() { self.util(); }; var self = Ctrl; // For the sake of syntax simplicity only self.util = function() { anyService.doSmth(); }; }
и тогда в модульных тестах теперь мы можем использовать:
Ctrl.util = jasmine.createSpy("util()"); expect(Ctrl.util).toHaveBeenCalled();
мне все еще не очень нравится это, но я думаю, что это самый простой способ сделать это. Я надеюсь, что кто-то найдет лучший подход.