MerC
MerC

Reputation: 323

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

});

Upvotes: 1

Views: 5911

Answers (1)

tmikeschu
tmikeschu

Reputation: 1418

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:

  1. handleSubmit calls readerOnLoad with a file argument if it exists on state.
  2. 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!

Upvotes: 5

Related Questions