How do I write FileReader test in Jasmine?

20,302

Solution 1

The problem here is the use of reader.onload which is hard to test. You could use reader.addEventListener instead so you can spy on the global FileReader object and return a mock:

eventListener = jasmine.createSpy();
spyOn(window, "FileReader").andReturn({
 addEventListener: eventListener
})

then you can fire the onload callback by yourself:

expect(eventListener.mostRecentCall.args[0]).toEqual('load');
eventListener.mostRecentCall.args[1]({
  target:{
    result:'the result you wanna test'
  }
})

Solution 2

This syntax changed in 2.0. Code below gives an example based on Andreas Köberle's answer but using the new syntax

    // create a mock object, its a function with some inspection methods attached
    var eventListener = jasmine.createSpy();

    // this is going to be returned when FileReader is instantiated
    var dummyFileReader = { addEventListener: eventListener };

    // pipe the dummy FileReader to the application when FileReader is called on window
    // this works because window.FileReader() is equivalent to new FileReader()
    spyOn(window, "FileReader").and.returnValue(dummyFileReader)

    // your application will do something like this ..
    var reader = new FileReader();

    // .. and attach the onload event handler
    reader.addEventListener('load', function(e) {

        // obviously this wouldnt be in your app - but it demonstrates that this is the 
        // function called by the last line - onloadHandler(event);
        expect(e.target.result).toEqual('url');

        // jasmine async callback
        done();
    });

    // if addEventListener was called on the spy then mostRecent() will be an object. 
    // if not it will be null so careful with that. the args array contains the 
    // arguments that addEventListener was called with. in our case arg[0] is the event name ..
    expect(eventListener.calls.mostRecent().args[0]).toEqual('load');

    // .. and arg[1] is the event handler function
    var onloadHandler = eventListener.calls.mostRecent().args[1];

    // which means we can make a dummy event object .. 
    var event = { target : { result : 'url' } };

    // .. and call the applications event handler with our test data as if the user had 
    // chosen a file via the picker
    onloadHandler(event);

Solution 3

I also faced similar problem and was able to achieve it without use of addeventlistener. I had used onloadend, so below is what I did. My ts file had below code:-

    let reader = new FileReader();
    reader.onloadend = function() {
                let dataUrl = reader.result;
                // Some working here
            };
    reader.readAsDataURL(blob);

My spec file (test) case code :-

 let mockFileReader = {
            result:'',
            readAsDataURL:(blobInput)=> {
                console.log('readAsDataURL');
            },
            onloadend:()=> {
                console.log('onloadend');
            }
        };

    spyOn<any>(window, 'FileReader').and.returnValue(mockFileReader);
    spyOn<any>(mockFileReader, 'readAsDataURL').and.callFake((blobInput)=> {
        // debug your running application and assign to "encodedString" whatever 
        //value comes actually after using readAsDataURL for e.g. 
        //"data:*/*;base64,XoteIKsldk......"
        mockFileReader.result = encodedString;
        mockFileReader.onloadend();
    });

This way you have mocked the FileReader object and returned a fake call to your own "readAsDataURL". And thus now when your actual code calls "reasAsDataURL" your fake function is called in which you are assigning an encoded string in "result" and calling "onloadend" function which you had already assigned a functionality in your code (.ts) file. And hence it gets called with expected result. Hope it helps.

Solution 4

I think the best way is to use the real FileReader (don't mock it), and pass in a real File or Blob. This improves your test coverage and makes your tests less brittle.

If your tests don't run in IE, you can use the File constructor, e.g.

const fakeFile = new File(["some contents"], "file.txt", {type: "text/plain"});

If you need to be compatible with IE, you can construct a Blob and make it look like a file:

const fakeFile = new Blob(["some contents"]);
fakeFile.name = "file.txt";
fakeFile.type = "text/plain";

The FileReader can read either of these objects so there is no need to mock it.

Solution 5

i found easiest for myself to do next.

  • mock blob file
  • run reader.onload while in test environment.

as result - i do not mock Filereader

//    CONTROLLER

$scope.handleFile = function (e) {

            var f = e[0];

            $scope.myFile = {
                name: "",
                size: "",
                base64: ""
            };
            var reader = new FileReader();
            reader.onload = function (e) {
                        try {
                            var buffer = e.target.result;
                            $scope.myFile = {
                                name: f.name,
                                size: f.size,
                                base64: XLSX.arrayBufferToBase64(buffer)
                            };
                            $scope.$apply();

                        } catch (error) {
                            $scope.error = "ERROR!";
                            $scope.$apply();
                        }
                    };

reader.readAsArrayBuffer(f);
//run in test env
if ( typeof jasmine == 'object') {reader.onload(e)}
}

//JASMINE TEST

it('handleFile    0', function () {


    var  fileContentsEncodedInHex = ["\x45\x6e\x63\x6f\x64\x65\x49\x6e\x48\x65\x78\x42\x65\x63\x61\x75\x73\x65\x42\x69\x6e\x61\x72\x79\x46\x69\x6c\x65\x73\x43\x6f\x6e\x74\x61\x69\x6e\x55\x6e\x70\x72\x69\x6e\x74\x61\x62\x6c\x65\x43\x68\x61\x72\x61\x63\x74\x65\x72\x73"];
    var blob = new Blob(fileContentsEncodedInHex);
    blob.type = 'application/zip';
    blob.name = 'name';
    blob.size = 11111;
    var e = {0: blob, target: {result: {}}};

    $scope.handleFile(e);
    expect($scope.error ).toEqual("");

});
Share:
20,302
toy
Author by

toy

This space is intentionally left blank.

Updated on July 17, 2022

Comments

  • toy
    toy almost 2 years

    I'm trying to make this test work, but I couldn't get my head around how to write a test with FileReader. This is my code

    
    function Uploader(file) {
        this.file = file;
    }
    
    Uploader.prototype =  (function() {
    
        function upload_file(file, file_contents) {
            var file_data = new FormData()
            file_data.append('filename', file.name)
            file_data.append('mimetype', file.type)
            file_data.append('data', file_contents)
            file_data.append('size', file.size)
    
            $.ajax({
                url: "/upload/file",
                type: "POST",
                data: file_contents,            
                contentType: file.type,
                success: function(){
    
                    // $("#thumbnail").attr("src", "/upload/thumbnail");    
    
                },
                error: function(){
                    alert("Failed");
                },
                xhr: function() {
                    myXhr = $.ajaxSettings.xhr();
                    if(myXhr.upload){
                        myXhr.upload.addEventListener('progress',showProgress, false);
                    } else {
                        console.log("Upload progress is not supported.");
                    }
                    return myXhr;
                }
            });
        }
    
        return {
            upload : function() {
                var self = this,
                    reader = new FileReader(),
                    file_content = {};
    
                reader.onload = function(e) {
                    file_content = e.target.result.split(',')[1];
    
                    upload_file(self.file, file_content);
                }
            }
        };
    })();
    
    
    
    

    And this is my test

    
    describe("Uploader", function() {
        it("should upload a file successfully", function() {
            spyOn($, "ajax");
            var fakeFile = {};
    
            var uploader = new Uploader(fakeFile);
            uploader.upload();
    
            expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/upload/file");
        })
    });
    
    

    But it never gets to reader.onload.