单元测试的模拟文件输入

Mock file input for unit test

本文关键字:输入 文件 模拟 单元测试      更新时间:2023-10-03

我得到了如下代码行:

jQuery('.js-img-input').on('change', handleNewFiles);   
var handleNewFiles = function(event) {
    var fileList = event.target.files;
    loadFileList(fileList);
};
var loadFileList = function(fileList) {
    jQuery(fileList).each(function(key, file) {
        readFileAsync(file);
    });
}
var readFileAsync = function(file) {
    var fileReader = new FileReader();
    fileReader.addEventListener("load", function(event) {
        file.result = event.target.result;
        saveFile(file);
    });
    fileReader.readAsDataURL(file);
};

所有方法在jQuery函数中都是私有的,我不想仅仅为了测试目的而将"handleNewFiles"公开
我很想用这样的东西来测试这些线路:

it('should create a fileReader', function(){
    spyOn(window, 'FileReader').and.returnValue({
        addEventListener: function(){},
        readAsDataURL: function(){}
    });
    jQuery('.js-img-input').trigger('change');
    expect(window.FileReader).toHaveBeenCalled();
});

但是我是如何将一些伪数据放入event.target.files的?

好吧,我找到了一个解决方案。您不能用文件创建伪造的本机事件,出于安全原因,这是一件好事。但在我的情况下,解决方案看起来是这样的:

it('should create a fileReader', function(){
  var onChangeCallback;
  spyOn(jQuery.fn, 'on').and.callFake(function(eventName, callback){
    if(eventName === 'change'){
      onChangeCallback = callback;
    }
  });
  spyOn(window, 'FileReader').and.returnValue({
    addEventListener: function(){},
    readAsDataURL: function(){}
  });
  onChangeCallback({target:{files:[1,2,3]}});
  expect(window.FileReader).toHaveBeenCalled();
});

虽然不能为fileInput.files赋值,但可以将属性替换为自己的属性,从而实现相同的功能:

it('should create a fileReader', () => {
    const fileInput = $('input[type=file]');
    const fileInputElement = fileInput.get(0);
    Object.defineProperty(fileInputElement, 'files', {
      value: [{name: 'file.txt'}],
      writable: false,
    });
    fileInput.trigger('input').trigger('change');
    // expect outputs...
});

这比创建自己的事件对象要好一点,因为它更接近真实环境。

本文可能有助于

http://nerds.intuo.io/2016/05/12/mocking-file-uploads-in-javascript.html

function fillInFileInput(selector, file) {
  // Get the input
  let input = jQuery(selector);
  // Get out file options
  let { name, type, content } = file;
  // Create a custom event for change and inject target
  let event = jQuery.Event('change', {
    target: {
      files: [{
        name: name, type: type
      }]
    }
  });
  // Stub readAsDataURL function
  let stub = sinon.stub(FileReader.prototype, 'readAsDataURL', function() {
    this.onload({ target: { result: content }});
  });
  // Trigger event
  input.trigger(event);
  // We don't want FileReader to be stubbed for all eternity
  stub.restore();
}

我发布了一个基于Jakob答案的稍微修改过的解决方案。我需要一些轻微的修改才能让它为我工作。这对我使用"angular":"1.0.6","jasmine node":"1.3.1","jquery":"1.10.0"有效

var onChangeCallback;
spyOn(jQuery.fn, 'on').andCallFake(function(eventName, selector, callback){
  if(eventName === 'change'){
    onChangeCallback = callback;
  }
});
spyOn(window, 'FileReader').andReturn({
  addEventListener: function(){},
  readAsDataURL: function(){}
});
onChangeCallback({currentTarget: {files:[{size: 4000}], value: 'asd'}});