How to test properly React Dropzone onDrop method
When passing
let image = {
name: 'cat.jpg',
size: 1000,
type: 'image/jpeg'
};
Into
wrapper.find(Dropzone).simulate('drop', { dataTransfer: { files: images } });
It will think image is undefined or null. The way I was able to fix this is
//Create a non-null file
const fileContents = "file contents";
const file = new Blob([fileContents], { type: "text/plain" });
wrapper.find(Dropzone).simulate("drop", { dataTransfer: { files: [file] } });
This of course is how you would do it for a plain text file. For different types of images you will want to specify the image type instead of doing "text/plain"
I ran into this issue while I was using the useDropzone
hook. Using
wrapper.find(...).simulate('drop', ...);
did not work for me.
Instead, I simulated change
on the input
field. This fit my use case for unit testing the component. I don't care about testing the specific drop functionality of the component since that is out of the scope for unit testing my component. Assuming that react-dropzone
functions as it should, I just need to test that my component handles the file drop event properly, which I can still test by interacting with the input
field instead. And it has the nice side effect of being more generic, in case I swap out dropzone libraries in the future.
wrapper.find('input').simulate('change', {
target: { files },
preventDefault: () => {},
persist: () => {},
});
And I define my files
like this:
const createFile = (name, size, type) => ({
name,
path: name,
size,
type,
});
const files = [
createFile('foo.png', 200, 'image/png'),
createFile('bar.jpg', 200, 'image/jpeg'),
];
Again, it fit my use case to just create mocked up file objects like this, instead of using native File
. You can add more properties (e.g. lastModifiedDate
) if you need to, but I didn't.
If, for some reason, you feel you need to create proper File
instances, you can do that as well:
const createFile = (name, size, type) => {
// the first arg, [], is the file content
// it's irrelevant, so I left it blank
// you can fill it like ['foobar'] or [name] if you want to
const file = new File([], name, { type });
Reflect.defineProperty(file, 'size', {
get() {
return size;
}
});
return file;
};
I had some issues going down this route in my testing due to the path
property not being set. Checking the equality of native File
objects is quite the hassle as well. The serialized file objects would end up being {}
, which is obviously not useful. I'm sure you can make it work, but IMO, avoid the native File
object if you can. There wasn't a benefit to using them in my tests.
I am using the react testing library and this is what worked for me properly , i had attached the test id to the div that took the getRootProps from the useDropZone hook,it does not necessarily have to be a div it can be any container element
function dispatchEvt(node: any, type: any, data: any) {
const event = new Event(type, { bubbles: true });
Object.assign(event, data);
fireEvent(node, event);
}
async function flushPromises(rerender: any, ui: any) {
await act(() => wait(() => rerender(ui)));
}
const onDropMock = jest.fn();
it("The on Drop is called on dragging a file ", async () => {
const file = new File([JSON.stringify({ ping: true })], "fileName.csv", { type: "text/csv" });
const data = mockData([file]);
const ui = <MyComponent onDrop={onDropMock} />;
const { container, rerender } = render(ui);
const input = screen.getByTestId("testId-for-div");
dispatchEvt(input, "drop", data);
await flushPromises(rerender, ui);
expect(onDropMock).toHaveBeenCalled();
});
I found all this info in the official docs here
If anyone is experiencing any further issues after creating stubbed files, I found also supplying a types field in the dataTransfer option to bypass some checks that react-dropzone uses . I'm using RTL so ymmv.
const fakeVideo = new File(['muhaahahah'], 'hello.mp4', { type: 'video/mp4' });
fireEvent.drop(getByTestId('test-input'), {
dataTransfer: { files: [fakeVideo], types: ['Files'] }
});