Mock file input for unit test

15,021

Solution 1

Alright I find a solution. You can't create a faked native event with files and for security reasons that's a good thing. But in my case the solution looks like this:

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();
});

Solution 2

this article may helps

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();
}

Solution 3

While you can't assign a value to fileInput.files, you can replace the property with your own, which accomplishes the same thing:

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...
});

This is a bit better than creating your own event object, since it simulates the real environment a little more closely.

Share:
15,021
Jakob
Author by

Jakob

Updated on June 05, 2022

Comments

  • Jakob
    Jakob about 2 years

    I got something like these lines of code:

    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);
    };
    

    All of the methods are private inside a jQuery-function and I don't whant to make "handleNewFiles" public just for testing purpose.
    I would love to test these lines with something like this:

    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();
    });
    

    But how did I get some dummy data into event.target.files?