Mock file upload to test a FileReader - javascript

Hello and thank you for your time reading this!
I am very interested to do unit tests while developing. I am new to tests in Javascript.
I have written a function to read files we choose,with an input, however I do not know how to test it.
I have read the documentation of Jasmine, about spies:
https://jasmine.github.io/api/2.6/global.html#spyOn
And:
https://jasmine.github.io/2.0/introduction.html
The code:
function readImage() {
if ( this.files && this.files[0] ) {
var FR= new FileReader();
var img = new Image();
FR.onload = function(e) {
img.src = e.target.result;
img.onload = function() {
ctx.drawImage(img, 0, 0, 512, 512);
};
};
FR.readAsDataURL( this.files[0] );
}
return [this.files, FR, img, ctx];
}
fileUpload.onchange = readImage;
The test I tried:
describe('readImage', function () {
it('should get at least one file ', function () {
spyOn(window, 'readImage');
fileUpload.dispatchEvent(new Event('onchange'));
expect(window.readImage).toHaveBeenCalled();
})
});
Also, fileUpload is:
function createUploadInput() {
body = document.getElementsByTagName("BODY")[0];
upload = document.createElement("input");
upload.setAttribute("type", "file");
upload.setAttribute("id", "fileUpload");
body.appendChild(upload);
return upload;
}
createUploadInput();
The output I get:
Expected spy readImage to have been called.
Error: Expected spy readImage to have been called.
at jasmine.Spec.<anonymous> (test/readImageSpec.js:42:34)
I think it is because of the readImage method has not been called in the test. The reason could be because fileUpload.dispatchEvent(new Event('onchange')); does nothing
Could you help me please?
I have also read:
How to trigger event in JavaScript?
Triggering event for unit testing
Using Jasmine to spy on a function without an object
Thank you for your help!
I have tried:
it('should get at least one file ', function () {
spyOn(window, 'readImage');
fileUpload.dispatchEvent(new Event('change'));
expect(window.readImage).toHaveBeenCalled();
})
But output is:
Expected spy readImage to have been called.
Error: Expected spy readImage to have been called.
at jasmine.Spec.<anonymous> (test/readImageSpec.js:42:34)
Also I tried:
it('should get at least one file ', function () {
spyOn(window, 'readImage');
fileUpload.onchange();
expect(window.readImage).toHaveBeenCalled();
})
And output:
Expected spy readImage to have been called.
Error: Expected spy readImage to have been called.
at jasmine.Spec.<anonymous> (test/readImageSpec.js:42:34)
I have also read: How can I trigger an onchange event manually?

Related

React how to test function called in FileReader onload function

I have the below code in a component. I want to test that onSubmit of the form, it calls the this.props.onUpload method in reader.
How can I test that?
My expect test is not working, I'm guessing it's because the this.props.onUpload is inside reader.onload function?
UploadForm.js
handleSubmit(e) {
e.preventDefault();
var inputData = '';
var file = this.state.file;
if (file) {
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
inputData = e.target.result;
this.props.onUpload(inputData);
};
})(file).bind(this);
reader.readAsText(file);
}
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<label> Enter File: <br/>
<input type="file" id="fileinput" onChange={this.handleChange}/>
</label>
<input type="submit" value="Submit" className="btn-upload" />
</form>
);
}
UploadForm.test.js
const mockOnUpload = jest.fn();
const file = new File([""], "filename");
const form = shallow(<UploadForm onUpload={mockOnUpload}/>);
const event = {
preventDefault: jest.fn(),
target: {files : [file]}
};
describe('when clicking `upload-file` button', () => {
beforeEach(() => {
form.find('#fileinput').simulate('change', event);
form.find('form').simulate('submit', event);
});
it('calls the handleSubmit CallBack', () => {
expect(mockOnUpload).toHaveBeenCalledWith(input);
});
});
Super great start with the mock upload being passed in as a prop and creating a fake event!
I always like running into testing issues like these in my own work because it tells me I have a code smell: if it's not easy to test, it likely means it is harder to predict, harder to debug, harder to explain, &c.
I recommend breaking out your functions to more singled responsibility. As it stands, your handleSubmit is doing a bit more than just handling submit. It also adds an onload function to an instance of FileReader and calls readAsText on that instance.
Your IIFE:
function(file) {
return function(e) {
inputData = e.target.result;
this.props.onUpload(inputData);
};
})(file).bind(this);
could be pulled out to an arrow function (taking care of bind) on the component:
readerOnLoad = file => (e) => {
this.props.onUpload(e.target.result);
}
(Also, is file needed as an argument here? Doesn't appear to be used.)
Then handleSubmit can interact withreaderOnLoad` like;
reader.onload = this.readOnLoad(file);
At this point, you can test that:
handleSubmit calls readerOnLoad with a file argument if it exists on state.
readerOnLoad, called with a file argument and then an event argument, calls this.props.onLoad with the event target result value.
If both of these tests pass, you can be confident that your code will work with real events, files, and FileReader instances.
It looks like you already understand how to pass in duck-type arguments (like events) that match native/browser objects, so just put those together and enjoy the peace of mind of your nicely collaborating functions!

Failed to execute 'readAsDataURL' on 'FileReader' . Parameter is not of type Blob

I'm using an image cropper to crop an image. I'm using the following library
https://github.com/mosch/react-avatar-editor
I have implemented this correctly and i'm getting the cropped image using this function.
handleSave = () => {
const img = this.editor.getImageScaledToCanvas().toDataURL();
this.props.croppedImage(img);
};
This is where i have implemented the cropper (2nd component)
<CropperTest croppedImage={this.getCroppedImg}/>
In the 2nd component i'm doing this
getCroppedImg(img) {
this.props.onImageImgPoll(img);
}
I'm calling onImageImgPoll in the 3rd component like this
onImageImgPoll(event,img) {
console.log("coming here1");
event.preventDefault();
let array = this.state.filepoll.slice();
let unq_file = img;
console.log("coming here2");
/* if (event.target.files && event.target.files[0]) {*/
console.log("coming here3");
let reader = new FileReader();
reader.onloadend = (e) => {
array.push(unq_file);
};
reader.readAsDataURL(unq_file);
console.log("coming here4");
this.setState({
image: event.target.result,
filepoll: array,
filestar: []
});
}
This is where i get the issue. I get an error like this
Uncaught TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'
What seems to be the issue here? Thanks in advance

Isolation of actions and tests

I try to refactor my code. I know that if I have several expectations they should be isolate in 'it'. I try to understand how I can write instead this:
describe('my scenario should make', function () {
var config = browser.params;
var url = config.listOfReferencesUrl,
grid,
numberField;
it('test1', function () {
browser.get(url);
browser.executeScript("icms.go('WEB_INQ_PROC', 'InquiryList', null, 0)");
grid = psGrid(by.css("table[class='n-grid']"));
numberField = grid.getQuickFilter(1);
numberField.click().sendKeys("Hello!");
since('fail1').expect(numberField.getInputText()).toEqual("");
});
it('test2', function () {
since('fail2').expect(numberField.getInputText()).toEqual("Hello!");
});
});
Something like this:
describe('my scenario should make', function () {
var config = browser.params;
var url = config.listOfReferencesUrl,
grid,
numberField;
*********Make this part of code ONES before all tests in spec ****
browser.get(url);
browser.executeScript("icms.go('WEB_INQ_PROC', 'InquiryList', null, 0)");
grid = psGrid(by.css("table[class='n-grid']"));
numberField = grid.getQuickFilter(1);
numberField.click().sendKeys("Hello!");
*******************************************************************
it('test1', function () {
since('fail1').expect(numberField.getInputText()).toEqual("");
});
it('test2', function () {
since('fail2').expect(numberField.getInputText()).toEqual("Hello!");
});
});
Maybe somebody have an idea how I can do this?
To answer your question, if you want to run your code once before all tests then use beforeAll() function available in Jasmine 2. Here's a sample -
beforeAll(function(){
//Write your code here that you need to run once before all specs
});
You can use beforeEach() function available in Jasmine to run it each time before a test spec. Here's a sample -
beforeEach(function(){
//Write your code here that you need to run everytime before each spec
});
If you are facing issue in getting these functions to work, then update your plugins to latest version and then try running it. Also use the framework: 'jasmine2' in your conf.js file
Hope this helps.

Javascript testing with mocha the html5 file api?

I have simple image uploader in a website and a javascript function which uses FileReader and converts image to base64 to display it for user without uploading it to actual server.
function generateThumb(file) {
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function (e) {
// Converts the image to base64
file.dataUrl = e.target.result;
};
}
Now I am trying to write tests for this method using Mocha and Chai. My thinking is that I want to check if the file.dataUrl was successfully created and it is base64. So I would like to mock the local file somehow in testing environment (not sure how to do this). Or I should not test this at all and assume that this is working ?
The answer here depends a little. Does your "generateThumbs" method have other logic than loading a files contents and assign that to a property of a passed in object? Or does it have other logic such as generating the thumbnail from the image data, reading out file properties and assigning them to the file object? and so on?
If so then I would infact suggest you mock out the FileReader object with your own, so that you can control your test results. However, it isn't the FileReaders functionality you want to test, it is your own logic. So you should assume that FileReader works and test that your code that depends upon it works.
Now since the method you posted was a bit on the small side, In that case I would just assume it works, rename the method and work on testing the rest of your code. But there is a place for having such a mock, and I must admit it was quite fun to figure out how to mock the event target, so I will give an example here, using your method as a basis:
//This implements the EventTarget interface
//and let's us control when, where and what triggers events
//and what they return
//it takes in a spy and some fake return data
var FakeFileReader = function(spy, fakeData) {
this.listeners = {};
this.fakeData = fakeData;
this.spy = spy;
this.addEventListener('load', function () {
this.spy.loaded = true;
});
};
//Fake version of the method we depend upon
FakeFileReader.prototype.readAsDataURL = function(file){
this.spy.calledReadAsDataURL = true;
this.spy.file = file;
this.result = this.fakeData;
this.dispatchEvent({type:'load'}); //assume file is loaded, and send event
};
FakeFileReader.prototype.listeners = null;
FakeFileReader.prototype.addEventListener = function(type, callback) {
if(!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
FakeFileReader.prototype.removeEventListener = function(type, callback) {
if(!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for(var i = 0, l = stack.length; i < l; i++) {
if(stack[i] === callback){
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
};
FakeFileReader.prototype.dispatchEvent = function(event) {
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
// Your method
function generateThumb(file, reader){
reader.readAsDataURL(file);
reader.addEventListener('load', function (e) {
file.dataUrl = base64(e.target.result);
});
}
Now we have a nice little object that behaves like a FileReader, but we control it's behavior, and we can now use our method and inject it into, thus enabling us to use this mock for testing and the real thing for production.
So we can now write nice unit tests to test this out:
I assume you have mocha and chai setup
describe('The generateThumb function', function () {
var file = { src: 'image.file'};
var readerSpy = {};
var testData = 'TESTDATA';
var reader = new FakeFileReader(readerSpy, testData);
it('should call the readAsDataURL function when given a file name and a FileReader', function () {
generateThumb(file, reader);
expect(readerSpy.calledReadAsDataURL).to.be.true;
expect(readerSpy.loaded).to.be.true;
});
it('should load the file and convert the data to base64', function () {
var expectedData = 'VEVTVERBVEE=';
generateThumb(file, reader);
expect(readerSpy.file.src).to.equal(file.src);
expect(readerSpy.file.dataUrl).to.equal(expectedData);
});
});
Here is a working JSFiddle example:
https://jsfiddle.net/workingClassHacker/jL4xpwwv/2/
The benefits here are you can create several versions of this mock:
what happens when correct data is given?
what happens when incorrect data is given?
what happens when callback is never called?
what happens when too large of a file is put in?
what happens when a certain mime type is put in?
You can offcourse massively simplify the mock if all you need is that one event:
function FakeFileReader(spy, testdata){
return {
readAsDataURL:function (file) {
spy.file = file;
spy.calledReadAsDataURL = true;
spy.loaded = true;
this.target = {result: testdata};
this.onload(this);
}
};
}
function generateThumb(file, reader){
reader.onload = function (e) {
file.dataUrl = base64(e.target.result);
};
reader.readAsDataURL(file);
}
Which is how I would actually do this. And create several of these for different purposes.
Simple version:
https://jsfiddle.net/workingClassHacker/7g44h9fj/3/

Unit testing with Mocha+PhantomJS async code

I'm new to unit testing, so please forrgive me if my question could be silly. I wrote an unit test using Mocha with PhantomJS and Chai as assertion library. The code that I want to test is the following function:
function speakingNotification(audioStream){
var options = {};
var speechEvents = hark(audioStream, options);
speechEvents.on('speaking', function() {
return 'speaking';
});
speechEvents.on('stopped_speaking', function() {
return 'stopped_speaking';
});
}
As you can see it takes an audioStream parameter as input and then use a librabry called hark.js https://github.com/otalk/hark for detecting speaking events. The function should return if the user is speaking or not.
So I wrote the following unit test:
describe('Testing speaking notification', function () {
describe('Sender', function(){
var audio = document.createElement('audio');
audio.src = 'data:audio/mp3;base64,//OkVA...'; //audio file with sound
var noAudio = document.createElement('audio');
noAudio.src = 'data:audio/mp3;base64,...'; //audio file with no sound
it('should have a function named "speakingNotification"', function() {
expect(speakingNotification).to.be.a('function');
});
it('speaking event', function () {
var a = speakingNotification(audio);
this.timeout( 10000 );
expect(a).to.equal('speaking');
});
it('stoppedSpeaking event', function () {
var a = speakingNotification(noAudio);
this.timeout( 10000 );
expect(a).to.equal('stopped_speaking');
});
});
});
The test fails and shows:
AssertionError: expected undefined to equal 'speaking'
AssertionError: expected undefined to equal 'stopped_speaking'
I also tried to use done() insted of the timeout, however the test fails and shows:
ReferenceError: Can't find variable: done
I searched for tutorials, however I can only find simple examples that don't help.
How can I write a correct test?

Categories

Resources