Как предоставить mock файлы для изменения события <input type='file'> для модульного тестирования
У меня возникают трудности с unit test, в котором я хочу проверить обработку файла, который обычно выбирается в представлении через <input type='file'>
.
В части контроллера моего приложения AngularJS файл обрабатывается внутри события изменения ввода следующим образом:
//bind the change event of the file input and process the selected file
inputElement.on("change", function (evt) {
var fileList = evt.target.files;
var selectedFile = fileList[0];
if (selectedFile.size > 500000) {
alert('File too big!');
// ...
Я хотел бы, чтобы evt.target.files
содержал мои данные mock вместо выбранного пользователем файла в моем unit test. Я понял, что я не могу создать экземпляр объекта FileList
и File
самостоятельно, что будет соответствовать объектам, с которыми работает браузер. Поэтому я пошел с назначением mock FileList для свойства ввода files
и запускал событие изменения вручную:
describe('document upload:', function () {
var input;
beforeEach(function () {
input = angular.element("<input type='file' id='file' accept='image/*'>");
spyOn(document, 'getElementById').andReturn(input);
createController();
});
it('should check file size of the selected file', function () {
var file = {
name: "test.png",
size: 500001,
type: "image/png"
};
var fileList = {
0: file,
length: 1,
item: function (index) { return file; }
};
input.files = fileList; // assign the mock files to the input element
input.triggerHandler("change"); // trigger the change event
expect(window.alert).toHaveBeenCalledWith('File too big!');
});
К сожалению, это вызывает следующую ошибку в контроллере, которая показывает, что эта попытка потерпела неудачу, поскольку файлы вообще не были привязаны к входному элементу:
TypeError: 'undefined' не является объектом (оценка "evt.target.files" )
Я уже выяснил, что свойство input.files
только для чтения по соображениям безопасности. Поэтому я начал новый подход, отправив настраиваемое изменение, которое обеспечило бы свойство файлов, но все же безуспешно.
Короче говоря: я бы хотел узнать о рабочем решении или о лучших методах подхода к этому тестовому сценарию.
Ответы
Ответ 1
Позвольте переосмыслить AngularJS, DOM must be handled in a directive
Мы не должны иметь дело с элементом DOM в контроллере, т.е. element.on('change', ..
, особенно для целей тестирования. В контроллере вы разговариваете с данными, а не с DOM.
Таким образом, те onchange
должны быть директивой, подобной следующей
<input type="file" name='file' ng-change="fileChanged()" /> <br/>
Однако, к сожалению, ng-change
не работает с type="file"
. Я не уверен, что будущая версия работает с этим или нет. Однако мы все равно можем применить тот же метод.
<input type="file"
onchange="angular.element(this).scope().fileChanged(this.files)" />
и в контроллере мы просто просто определяем метод
$scope.fileChanged = function(files) {
return files.0.length < 500000;
};
Теперь все это обычный тест контроллера. Больше не имеет дело с angular.element
, $compile
, triggers
и т.д.!:)
describe(‘MyCtrl’, function() {
it('does check files', inject(
function($rootScope, $controller) {
scope = $rootScope.new();
ctrl = $controller(‘UploadCtrl’, {‘$scope’: scope});
var files = { 0: {name:'foo', size: 500001} };
expect(scope.fileChanged(files)).toBe(true);
}
));
});
http://plnkr.co/edit/1J7ETus0etBLO18FQDhK?p=preview
Ответ 2
ОБНОВЛЕНИЕ: Благодаря @PeteBD,
Так как версия angularjs 1.2.22, jqLite теперь поддерживают передачу настраиваемого объекта события в triggerHandler()
. См.: d262378b
Если вы используете только jqLite,
triggerHandler()
никогда не будет работать, поскольку он передаст фиктивный объект события обработчикам.
Объект фиктивного события выглядит следующим образом (скопировано из jqLite.js#L962)
{
preventDefault: noop,
stopPropagation: noop
}
Как вы можете видеть, он даже не имеет свойства target
.
Если вы используете jQuery,
вы можете вызвать событие с настраиваемым объектом события следующим образом:
input.triggerHandler({
type: 'change',
target: {
files: fileList
}
});
а evt.target.files
будет fileList
, как вы ожидаете.
Надеюсь, что это поможет.
Ответ 3
Ваш обработчик изменения файла должен, вероятно, быть функцией непосредственно на вашем контроллере. Вы можете связать эту функцию с событием изменения либо из HTML, либо из директивы. Таким образом, вы можете напрямую вызвать функцию вашего обработчика, не беспокоясь о запуске события. Это видео egghead.io охватывает пару способов, которыми вы можете это сделать: https://egghead.io/lessons/angularjs-file-uploads
Есть много вещей, о которых вам нужно беспокоиться, когда вы загружаете свой собственный загрузчик файлов с помощью Angular, поэтому я бы просто использовал одну из существующих там библиотек, которая позаботится об этом. например angular-file-upload