#javascript #angularjs #unit-testing #d3.js #jasmine
#javascript #angularjs #модульное тестирование #d3.js #jasmine
Вопрос:
У меня есть компонент линейной диаграммы, написанный с использованием D3 и Angular. Он имеет перетаскиваемые столбцы, используемые для изменения масштаба оси x. Теперь я пытаюсь протестировать компонент, используя Karma / Jasmine. У меня возникли проблемы с запуском события перетаскивания в модульном тестировании.
Другие события, такие как наведение курсора мыши и щелчок, запускаются просто отлично. Я думаю, что это синтезированная реализация события «перетаскивания» в d3, которая вызывает проблему. Вот код, который я использую для запуска перетаскивания (такой код запускает правильное событие перетаскивания d3 на тестовой html-странице, но завершается неудачей внутри модульного теста).
var leftBar = element.find(".left-bar")[0];
var evObjStart = document.createEvent("MouseEvents");
evObjStart.initMouseEvent("mousedown", true, true, window, 1, 12, 320, 12, 320, false, false, false, false, 0, null);
var evObj = document.createEvent("MouseEvents");
evObj.initMouseEvent("mousemove", true, true, window, 1, 100, 320, 100, 320, false, false, false, false, 0, null);
var evObjEnd = document.createEvent("MouseEvents");
evObjEnd.initMouseEvent("mouseup", true, true, window, 1, 200, 320, 200, 320, false, false, false, false, 0, null);
leftBar.dispatchEvent(evObjStart);
leftBar.dispatchEvent(evObj);
leftBar.dispatchEvent(evObjEnd);
Я обнаружил, что только первое событие правильно отправлено (leftBar.dispatchEvent(evObjStart)).
Два других события вообще не отправляются. Переменная leftBar верна на 100%. Я совершенно не понимаю, где искать ошибку.
PS Я попытался сгенерировать события «dragstart», «drag» и «dragend», но они не обрабатываются должным образом d3 в браузере (не работает в базовом тестовом примере в консоли). Последовательность Mousedown / mousemove / mouseup корректно работает в браузере, но в модульном тестировании отправляется только mousedown. Буду очень благодарен за любую помощь или идеи.
Комментарии:
1. Я думаю, вы бы значительно упростили свою жизнь, если бы просто вызывали обработчики напрямую с помощью mocked arguments и доверяли тому, что поставщики браузеров правильно реализовали обработку событий.
2. Спасибо, Бен! Мой вариант использования немного сложнее. На самом деле, мой код находится внутри директивы angular, поэтому обработчики недоступны извне. Предоставление им доступа фактически нарушило бы концепцию использования этой директивы в качестве отдельного плагина (который не должен влиять на внешний html или переменные). Я думал о написании версии директивы «только для тестирования» с обработчиками, видимыми извне, но это противоречит идее модульного тестирования, если у меня есть один компонент для распространения, а другой для тестов. Надеюсь, мое объяснение имеет смысл)
3. Вы можете сделать обработчики доступными извне, выполнив множество действий. Например, добавление их в качестве элемента или статического свойства в контроллер вашей директивы. Которое можно получить с помощью $controller, если оно было правильно зарегистрировано.
4. Я думаю, что я бы сделал это, создав директивы для привязки событий мыши d3, а затем используя их в директиве, которая больше похожа на конструкцию шаблона / контроллера. Возможно, одна из ваших директив пытается сделать слишком много, что затрудняет тестирование.
5. Спасибо! Сейчас я это изучу. Вы правы, в настоящее время у меня есть весь код внутри одной директивы. Angular используется для доставки / обновления данных, и вся обработка выполняется внутри с помощью d3. Я постараюсь переопределить архитектуру, спасибо за идею!
Ответ №1:
Хотя это не ответ, я надеюсь, что это поможет.
Вот способ, которым вы можете разбить свои обработчики на что-то проверяемое.
Добавьте их в качестве элементов вашего контроллера следующим образом:
app.controller('MyDirectiveCtrl', function($scope) {
this.dragHandler = function() {
// do stuff
};
});
app.directive('MyDirective', function(){
return {
controller: 'MyDirectiveCtrl',
link: function(scope, elem, attrs, ctrl) {
var drag = d3.behavior.drag();
drag.on('drag', ctrl.dragHandler);
d3.select(elem[0]).call(drag);
}
}
});
Затем для тестирования:
<!-- language: lang-js -->
// create the controller
var ctrl = $controller('MyDirectiveCtrl', {
$scope: mockScope
});
// call the handler
ctrl.dragHandler(mock, args, here);
// assert some difference
expect(mock).toBe(differentSomehow
);
Комментарии:
1. Бен, большое вам спасибо за ваши быстрые и эффективные ответы. Ваша идея углубила мое понимание Angular, спасибо! Мой руководитель хочет, чтобы я сохранял данные внутри директивы для обеспечения гибкости и независимости, поэтому я буду искать способ изменить его по-другому. PS Я надеюсь, вы не возражаете, если я все еще оставлю вопрос открытым, может быть, кто-то случайно запускает событие перетаскивания для d3.
2. Я совсем не против, что вы оставляете вопрос открытым.
3. Скажите своему руководителю «Бен Леш говорит, что ты сумасшедший»: ха-ха. Если что-то сложно протестировать, это потому, что оно плохо сконструировано. Вам придется так или иначе предоставить эти обработчики и протестировать их. Тестирование DOM напрямую очень сложно и очень хрупко. Если вы должны пойти по этому пути, посмотрите на создание тестов транспортира, хотя я не уверен, насколько хорошо это будет работать с перетаскиванием.
4. XD спасибо, Бен! мой руководитель — отличный парень, я думаю, у него тоже есть хорошая точка зрения (скорее всего, это я не понимаю правильно). Я продолжу пересматривать архитектуру. Спасибо за ваши усилия, приложенные к этому вопросу, я очень ценю это! Наверняка рассмотрит Транспортир.