我在单元测试中遇到问题,我想验证文件的处理,通常会在<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
在我的单元测试中包含我的模拟数据而不是用户选择的文件。我意识到我无法自己实例化FileList
和File
对象,这将是浏览器正在使用的相应对象。所以我选择将模拟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:&#39; undefined&#39;不是一个对象(评估&#39; evt.target.files&#39;)
出于安全原因,我已经发现input.files
属性只读。所以我开始采用另一种方法,通过调度提供文件属性的自定义更改,但仍然没有成功。
长篇故事简短:我渴望学习如何处理此测试用例的工作解决方案或任何最佳实践。
答案 0 :(得分:11)
更新:感谢@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
。
希望这有帮助。
答案 1 :(得分:9)
让我们重新考虑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);
}
));
});
答案 2 :(得分:3)
以下是使用angular2 +输入文件/图像的示例规范。
it('should call showError on toastService Api on call of onSaveOfImage() method', () => {
spyOn(component.commonFacade.fileIOApi, 'uploadFile');
let file = new File([new ArrayBuffer(2e+5)], 'test-file.jpg', { lastModified: null, type: 'image/jpeg' });
let fileInput={ files: [file] };
component['onSaveOfImage'](fileInput,"",null,"","");
expect(component.commonFacade.fileIOApi.uploadFile).toHaveBeenCalledTimes(1);
expect(component.uploadedFileData).toBeUndefined();
expect(component.commonFacade.employeeApi.toastService.showError).toHaveBeenCalledTimes(1);
})
答案 3 :(得分:-1)
您的文件更改处理程序应该可以直接在您的控制器上执行。您可以从HTML或指令将该函数绑定到change事件。这样,您可以直接调用处理程序函数,而无需担心触发事件。这个egghead.io视频涵盖了几种方法:https://egghead.io/lessons/angularjs-file-uploads
在使用Angular滚动自己的文件上传器时,您需要担心很多事情,所以我只想使用其中一个处理它的现有库。例如angular-file-upload