如何正确测试React Dropzone的onDrop方法

13

我正在测试 React Dropzone,需要检查 onDrop 函数。该函数有两个参数(acceptedFiles 和 rejectedFiles)。我像这样模拟文件:

let image = {
  name: 'cat.jpg',
  size: 1000,
  type: 'image/jpeg'
};

然后在我的测试中,我这样做:

it('should call handleOnDrop with more than 5 acceptedFiles', () => {
    const wrapper = mount(mockComponent());

    for (let index = 0; index < 5; index++) {
      images.push(image);
    }

    wrapper.find(Dropzone).simulate('drop', { dataTransfer: { files: images } });

    expect(setUserNotificationsSpy).toHaveBeenCalledTimes(1);
});

这是我的 onDrop 函数:

const handleOnDrop = (acceptedFiles, rejectedFiles) => {
    if (rejectedFiles && rejectedFiles.length) {
      checkMaxFile(rejectedFiles, maxSize) && setUserNotifications('error_big_image');
    }

    acceptedFiles && acceptedFiles.length <= maxFiles ? onDrop(acceptedFiles) : setUserNotifications('more_than_5');
};
预期结果是handleOnDrop返回acceptedFiles,但它返回了rejectedFiles,我不知道为什么。
MIME类型和大小都没问题。
这是来自react-dropzone的函数:
  fileAccepted(file) {
      // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
      // that MIME type will always be accepted
      return file.type === 'application/x-moz-file' || accepts(file, this.props.accept);
  }

谢谢。

4个回答

9

当传递参数时

let image = {
  name: 'cat.jpg',
  size: 1000,
  type: 'image/jpeg'
};

Into

wrapper.find(Dropzone).simulate('drop', { dataTransfer: { files: images } });

它会认为图像未定义或为空。我解决这个问题的方法是:
//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] } });

当然,这是针对普通文本文件的操作方式。对于不同类型的图像文件,您需要指定图像类型,而不是使用"text/plain"。

8
我在使用useDropzone钩子时遇到了这个问题。
wrapper.find(...).simulate('drop', ...);

对我没用。

相反,我在input字段上模拟了change。这符合我的单元测试组件的使用情况。我不关心测试组件的特定放置功能,因为这超出了单元测试组件的范围。假设react-dropzone正常工作,我只需要测试我的组件是否正确处理文件放置事件,我仍然可以通过与input字段进行交互来测试它。这也有一个好的副作用,即更加通用,以防将来替换掉dropzone库。

wrapper.find('input').simulate('change', {
  target: { files },
  preventDefault: () => {},
  persist: () => {},
});

我是这样定义我的文件的:

const createFile = (name, size, type) => ({
  name,
  path: name,
  size,
  type,
});

const files = [
  createFile('foo.png', 200, 'image/png'),
  createFile('bar.jpg', 200, 'image/jpeg'),
];

再次强调,根据我的使用情况,创建这样的模拟文件对象(而不是使用本地的File对象)非常合适。如果需要,您可以添加更多属性(例如lastModifiedDate),但我没有这样做。

如果出于某种原因,您觉得需要创建正确的File实例,也可以这样做:

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

在我的测试中,由于未设置path属性,我在这条路线下遇到了一些问题。检查本地File对象的相等性也相当麻烦。序列化文件对象最终会变成{},显然没有用。我相信你可以让它正常工作,但个人认为,如果可以避免使用本机File对象就避免使用吧。在我的测试中,使用它们并没有好处。


我在想你是如何在这里模拟你的输入onChange的?我也在面临同样的问题。 - suisied
@suisied,我的回答中的第一个代码片段表明 - Michael Yaworski
是的,我知道,但每当我添加例如mount(<Dropzone onDrop={mockValidate} >)并使用.find来定位onDrop,并尝试模拟它时,当“toHaveBeenCalledTimes(1)”时似乎没有被调用。 - suisied
我已经有一段时间没有访问这些测试的代码库了,所以我面前没有任何可以用来帮助你进一步解决问题的东西。你可能想在这里提出一个新的问题并提供代码片段。如果你创建了一个新问题,请随时将问题链接发给我。更大的代码片段可能意味着我能够看到问题所在。 - Michael Yaworski

3

我正在使用React测试库,以下是我的正确操作方法, 我已将测试ID附加到从useDropZone hook获取getRootProps的div上,它不必是一个div,可以是任何容器元素。

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

我在官方文档这里找到了所有这些信息。


2

如果在创建桩文件后仍然遇到其他问题,我发现在dataTransfer选项中提供一个types字段也可以绕过react-dropzone使用的一些检查。我正在使用RTL,所以可能会有所不同。

const fakeVideo = new File(['muhaahahah'], 'hello.mp4', { type: 'video/mp4' });

fireEvent.drop(getByTestId('test-input'), {
   dataTransfer: { files: [fakeVideo], types: ['Files'] }
});

1
这不会触发onDrop函数。 - mohantyArpit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接