Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock file input for unit test

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?

like image 952
Jakob Avatar asked Sep 18 '25 09:09

Jakob


2 Answers

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();
});
like image 125
Jakob Avatar answered Sep 20 '25 23:09

Jakob


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.

like image 30
AJ Richardson Avatar answered Sep 20 '25 23:09

AJ Richardson