How to provide mock files to change event of <input type='file'> for unit testing

Related searches

I'm having difficulties with a unit test in which I want to verify the processing of a file, which would usually be selected in the view via <input type='file'>.

In the controller part of my AngularJS app the file is processed inside the input's change event like so:

//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!');
    // ...

I'd like evt.target.files to contain my mock data instead of the user's selected file in my unit test. I realized that I can't instantiate a FileList and File object by myself, which would be the according objects the browser is working with. So I went with assigning a mock FileList to the input's files property and triggering the change event manually:

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

Unfortunately, this causes the following error in the controller which shows that this attempt failed because the files were not assigned to the input element at all:

TypeError: 'undefined' is not an object (evaluating 'evt.target.files')

I already found out that the input.files property is read-only for security reasons. So I started another approach by dispatching a customized change which would provide the files property, but still without success.

So long story short: I'd be eager to learn a working solution or any best practices on how to approach this test case.

Let's rethink AngularJS, DOM must be handled in a directive

We should not deal with DOM element in a controller, i.e. element.on('change', .., especially for testing purpose. In a controller, You talk to data, not to DOM.

Thus, those onchange should be a directive like the following

<input type="file" name='file' ng-change="fileChanged()" /> <br/>

However, unfortunately, ng-change does not work well with type="file". I am not sure that the future version works with this or not. We still can apply the same method though.

<input type="file" 
  onchange="angular.element(this).scope().fileChanged(this.files)" />

and in the controller, we just simply define a method

$scope.fileChanged = function(files) {
  return files.0.length < 500000;
};

Now, everything is just a normal controller test. No more dealing with angular.element, $compile, triggers, etc.! :)

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

Angular & Jasmine Unit Test change event for input[type="file , toHaveBeenCalledWith(mockFormGroup, mockFile, mockReader); });. Of course you can change mock values and create more unit tests It’s injected into the class under test to give you absolute control over what’s being tested as input. A typical stub is a database connection that allows you to mimic any scenario without having a real database. A mock is a fake class that can be examined after the test is finished for its interactions with the class under test.

UPDATE: Thanks to @PeteBD,

Since angularjs version 1.2.22, the jqLite are now support passing a custom event object to triggerHandler(). See: d262378b


If you are using only jqLite,

the triggerHandler() will never work as it will pass a dummy event object to handlers.

The dummy event object look like this (copied from jqLite.js#L962)

{
  preventDefault: noop,
  stopPropagation: noop
}

As you can see, it doesn't even have a target property.

If you are using jQuery,

you could trigger an event with a custom event object like this:

input.triggerHandler({
  type: 'change',
  target: {
    files: fileList
  }
});

and the evt.target.files will be the fileList as you are expecting.

Hope this helps.

How to provide mock files to change event of , How to provide mock files to change event of <input type='file'> for unit testing. jest mock event how to mock event in jasmine angular test trigger change event You would probably need to mock the static Files.readAllBytes() method which you would need to use something like PowerMock to do. Or you could wrap this in a method which you can then mock the behaviour of: public byte[] getAllBytesWrapper(File body) { return Files.readAllBytes(body.toPath()); } and then have a mock for this method:

Here is an example spec for input file/image using 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);
  })

Codebytes: How to mock upload files in the test cases, <input type="file" id="file-upload" onchange="doSomething" /> We are gonna bypass the DataTransfer event's file addition methods to construct a let's get the DataTransfer object from the ClipboardEvent (mock's file� Some callbacks are just events, called to give the user a chance to react when a certain state is triggered. Show Answer Q 24 - Which built-in method returns the character at the specified index?

Your file change handler should probably be a function directly on your controller. You can bind that function to the change event either from the HTML or a directive. That way you can call your handler function directly without worrying about triggering an event. This egghead.io video covers a couple ways you can do that: https://egghead.io/lessons/angularjs-file-uploads

There are a lot of things you need to worry about when rolling your own file uploader with Angular so I would just use one of the existing libraries out there that takes care of it. e.g. angular-file-upload

Creating fake test events with Typescript + Jest, For this example, I'm going to use an HTML input tag for file upload. To create the fake event object on line 23, it's probably best to work backwards. You'll need to Because we're creating a ChangeEvent, look at the type. I have a presenter class, that attaches an event of the injected view. Now I would like to test the presenter reacting on correctly on the event. This is the view interface IView: public interface IView { event EventHandler MyEvent; void UpdateView(string test); } This ist the view implementing IView

How to provide mock files to change event of <input type , How to provide mock files to change event of <input type='file'> for unit testing. I'm having difficulties with a unit test in which I want to verify the processing of a� Once you extract its contents, take a close look at what the seller has included. Ideally, it should possess the following files:.psd file of the mockup, which is the source file you will edit.jpg file of the mockup, a preview of the template in action.txt or .pdf file that explains what the mockup is about, who made it, and its licensing policy.

The best way to simulate mouse events is to call the OnEventName method that raises the mouse event you want to simulate. This option is usually possible only within custom controls and forms, because the methods that raise events are protected and cannot be accessed outside the control or form.

The change event is sent to an element when its value changes. This event is limited to <input> elements, <textarea> boxes and <select> elements. For select boxes, checkboxes, and radio buttons, the event is fired immediately when the user makes a selection with the mouse, but for the other element types the event is deferred until the element

Comments
  • Are you using jQuery or just a jqLite?
  • tacking a few props onto new Blobs() should leave you with something that quacks an awful lot like a File()...
  • Note I think the scope() is a debug-only function, not really appropriate for production code.
  • A bit confused with this answer as it starts by saying "must be handled in a directive" and then gives an example without using a directive. If you place in a directive (which is good advice!) you are right back to needing the trigger / $compile and finding a way to pass the files object to your handler. The answer by @runTarm explains how to do the latter.
  • @user1821052, it starts by saying "DOM must be handled...", and goes on to state "We should not deal with DOM element in a controller", indicating the solution does not manipulate the DOM inside a controller.
  • thank you very much for your sophisticated answer! I'd aspire a working solution with jqLite (or even without jqLite at all) rather than with jQuery, because I don't necessarily want to switch to jQuery just for this issue. If no other viable solution is proposed, I will come back to your answer and accept it. Thank you again!
  • @runTarm is correct here. jqLite should be fixed in this regard by github.com/angular/angular.js/pull/8505
  • I don't personally see a problem with utilizing jQuery for test-only...that way you can keep your deployed product lean and still get the functionality that the jQuery can provide for a test.
  • I'm well aware of these directives, but they don't solve the problem since I'm only concerned with this fundamental mechanic of the file input (which any directive would also rely on) and how to properly unit test it using mock data. Binding the change event in the view and using a callback function on the controller which can be called directly (instead of triggering the change event) within the unit test would work, but generally I'd like to be able to solve this even when the change event is bound via JavaScript like described in my code. So for me the question still remains.