Reputation: 16723
I have a function which creates a FileReader
. In that function I also set the load
and error
event handlers
handleFileSelect(files:ArrayLike<File>){
...
let reader = new FileReader()
reader.onload = this.handleReaderLoaded;
reader.onerror = this.handleReaderError;
reader.readAsDataURL(file);
}
}
I want to unit-test that handleFileSelect
correctly sets the error handler and that the error handler (handleReaderError
) gets called if FileReader
fails. But I can't figure out how to make the FileReader
fail.
The spec I have written so far is
fit('should call error handler when file doesn\'t get loaded successfully', (done) => {
let newPracticeQuestionComponent = component;
let file1 = new File(["foo1"], "foo1.txt");
/*
File reader will load the file asynchronously.
The `done` method of `Jasmine` makes `Jasmine` wait
When handleReaderError is called, call a fake function and within it call done
*/
spyOn(newPracticeQuestionComponent,'handleReaderError').and.callFake(function(event:FileReaderProgressEvent){
console.log("called fake implementation of handleReaderError ",event);
expect(event.type).toEqual("abort");
done();
});
newPracticeQuestionComponent.handleFileSelect([file1]);
//I SHOULD SIMULATE FILEREADER ERROR HERE BUT HOW??
});
Upvotes: 3
Views: 3528
Reputation: 1707
When you have a typescript file and a severity to be set depending on the success of reading an inputs file from disk:
enum Severity
{
NONE = 'none',
ERROR = 'danger',
OK = 'success'
}
private jsonFile: File | undefined = undefined;
private severity: Severity = Severity.NONE;
protected changeFileSelection(): void
{
this.jsonFile = this.input.files?.[0];
const fileReader: FileReader = new FileReader();
fileReader.onload = () => this.load(JSON.parse(fileReader.result as string));
fileReader.onerror = (error: ProgressEvent<FileReader>): void => {
this.severity = Severity.ERROR;
console.error(error);
};
this.severity = Severity.OK;
fileReader.readAsText(this.jsonFile, 'UTF-8');
}
You can test the onerror call in Jasmine as follows:
it('should handle file read errors', () => {
const emptyFile: File = new File(['{}'], 'filename.json');
component['jsonFile'] = emptyFile;
const dataTransfer: DataTransfer = new DataTransfer();
dataTransfer.items.add(emptyFile);
input.files = dataTransfer.files; // @see https://stackoverflow.com/a/68182158/959484
const fileReader: FileReader = new FileReader();
spyOn(window, 'FileReader').and.returnValue(fileReader);
spyOn(fileReader, 'readAsText').and.callFake((): void => {
expect(component['severity']).toEqual(component['Severity'].OK);
fileReader.dispatchEvent(new Event('error'));
});
component['changeFileSelection']();
expect(component['severity']).toEqual(component['Severity'].ERROR);
});
Note, that the readAsText
call must be after the positive Severity result you want to have in case of a successful read. But in this test case, the outcome will be negative due to simulated Error while reading.
Upvotes: 0
Reputation: 1267
It was already said that we can mock readAsDataURL
method and dispatch the error event from it. But your reader is a local variable in the function handleFileSelect
. In order to access the reader, we can mock the FileReader
constructor and get control of the created file reader instance.
Here I use sinon for mocking:
// in your test:
...
// This is the reader instance that we have access
const reader = new FileReader()
// We throw an error in readAsArrayBuffer method of that instance
reader.readAsArrayBuffer = () => {
reader.dispatchEvent(new Event('error'))
}
// Now make the constructor return our instance
sinon.stub(window, 'FileReader').returns(r)
// Now make your calls and assertions
...
// Don't forget to restore the original constructor at the end
window.FileReader.restore()
Upvotes: 2
Reputation: 636
If the reader
's behaviour is calling onerror
when readAsDataURL
fails, this should do:
spyOn(newPracticeQuestionComponent.reader, 'readAsDataURL').and.callFake(() => {
newPracticeQuestionComponent.reader.onerror();
});
Since this will be running as a synchronous call, you can simplify the assertion at the end of the test (following a triple A) like this:
// Arrange
const newPracticeQuestionComponent = component;
spyOn(newPracticeQuestionComponent, 'handleReaderError');
spyOn(newPracticeQuestionComponent.reader, 'readAsDataURL').and.callFake(() => {
newPracticeQuestionComponent.reader.onerror();
});
let file1 = new File(["foo1"], "foo1.txt");
// Act
newPracticeQuestionComponent.handleFileSelect([file1]);
// Assert
expect(newPracticeQuestionComponent.handleReaderError).toHaveBeenCalledWith({ type: 'abort' });
But I don't recommend expecting the parameter passes to the function, event.type
, because it is the specification of another unit that we are not currently testing. (we are testing newPracticeQuestionComponent
not the behaviour of reader
calling an error with an event)
Mocking the behaviour of reader
might not be the best way. It depends on what you want to test against the unit.
In case we want to go extremely independent, newPracticeQuestionComponent
should know nothing about reader
's behaviour even the callback error, the only thing this unit should know is to set the onerror
callback, you can just assert that you set the onerror
of reader correctly.
// Arrange
const newPracticeQuestionComponent = component;
spyOn(newPracticeQuestionComponent.reader, 'readAsDataURL');
let file1 = new File(["foo1"], "foo1.txt");
// Act
newPracticeQuestionComponent.handleFileSelect([file1]);
// Assert
expect(newPracticeQuestionComponent.reader.onerror).toBe(newPracticeQuestionComponent.handleReaderError);
I am no master about testing, but it seems to be pros and cons writing tests like the above and below examples upon many factors.
Hope this helps :)
Upvotes: 1