如何正确地开玩笑地模拟`antd` Upload Dragger?

How to properly mock `antd` Upload Dragger with jest?

我的项目中有以下反应代码

import React from 'react';
import { Upload } from 'antd';

const { Dragger } = Upload;

...

<Dragger
  accept={ACCEPTED_FORMATS}
  beforeUpload={beforeUpload}
  data-testid="upload-dragger"
  maxCount={1}
  onChange={({ file: { status } }) => {
    if (status === 'done') onUploadComplete();
  }}
  progress={progress}
  showUploadList={false}
>
{/* here i have a button, code ommited for clarity, if needed i'll post it */}
</Dragger>

我想测试当file.status为'done'时回调函数onUploadComplete()是否被调用。

这是我现在做测试的方式:

  1. 我有一个 jest.mock 来模拟一个总是会成功的 愚蠢的请求
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { DraggerProps } from 'antd/lib/upload';
import userEvent from '@testing-library/user-event';

import UploadCompanyLogo from '../UploadCompanyLogo'; // This is the component where the dragger is placed

jest.mock('antd', () => {
  const antd = jest.requireActual('antd');
  const { Upload } = antd;
  const { Dragger } = Upload;

  const MockedDragger = (props: DraggerProps) => {
    console.log('log test');
    return (
      <Dragger
        {...props}
        action="greetings"
        customRequest={({ onSuccess }) => {
          setTimeout(() => {
            onSuccess('ok');
          }, 0);
        }}
      />
    );
  };

  return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };
});
  1. 测试本身(与 mock 相同的文件),它呈现组件(其中 antd 将被导入),模拟上传,然后检查回调是否已被调用。
it('completes the image upload', async () => {
    const flushPromises = () => new Promise(setImmediate);

    const { getByTestId } = render(elementRenderer({ onUploadComplete }));

    const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });

    const uploadDragger = await waitFor(() => getByTestId('upload-dragger'));

    await act(async () => {
      userEvent.upload(uploadDragger, file);
    });

    await flushPromises();

    expect(onUploadComplete).toHaveBeenCalledTimes(1);
    expect(onUploadComplete).toHaveBeenCalledWith();
  });
  1. 元素渲染器
const elementRenderer = ({
  onUploadComplete = () => {}
}) => (
  <ApplicationProvider>
    <UploadCompanyLogo
      onUploadComplete={onUploadComplete}
    />
  </ApplicationProvider>
);
  1. 应用程序提供者
import React, { PropsWithChildren } from 'react';
import { ConfigProvider as AntdProvider } from 'antd';
import { RendererProvider as FelaProvider } from 'react-fela';
import { createRenderer } from 'fela';
import { I18nextProvider } from 'react-i18next';

import antdExternalContainer, {
  EXTERNAL_CONTAINER_ID,
} from 'src/util/antdExternalContainer';
import { antdLocale } from 'src/util/locales';
import rendererConfig from 'src/fela/felaConfig';
import i18n from 'src/i18n';

const ApplicationProvider = (props: PropsWithChildren<{}>) => {
  const { children } = props;

  return (
    <AntdProvider locale={antdLocale} getPopupContainer={antdExternalContainer}>
      <FelaProvider renderer={createRenderer(rendererConfig)}>
        <I18nextProvider i18n={i18n}>
          <div className="antd-local">
            <div id={EXTERNAL_CONTAINER_ID} />
            {children}
          </div>
        </I18nextProvider>
      </FelaProvider>
    </AntdProvider>
  );
};

export default ApplicationProvider;

这目前无法正常工作,但已经在 @diedu 的帮助下进行了改进。

我放入 MockedDragger 的 console.log() 目前没有显示。如果我在组件和 mockedDragger 中都放置 console.log(),它会打印组件日志。

关于如何进行的任何提示? 我已经看过这个 issue 但没有帮助。

您应该更改的第一件事是您在模拟中所做的 return。您正在 return 创建一个名为 MockedDragger 的新组件,而不是 Dragger

  return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };

接下来是事件触发。根据 this issue,您应该使用 RTL 用户事件库并将调用包装在 act

import userEvent from "@testing-library/user-event";

...
  await act(async () => {
    userEvent.upload(uploadDragger, file);
  });
...

最后,由于一些异步代码 运行 在检查调用之前

  const flushPromises = () => new Promise(setImmediate);
  ...
  await flushPromises();
  expect(onUploadComplete).toHaveBeenCalled()

这是完整版

import { render, waitFor } from "@testing-library/react";
import App from "./App";
import userEvent from "@testing-library/user-event";
import { act } from "react-dom/test-utils";
import { DraggerProps } from "antd/lib/upload";

jest.mock('antd', () => {
  const antd = jest.requireActual('antd');
  const { Upload } = antd;
  const { Dragger } = Upload;

  const MockedDragger = (props: DraggerProps) => {
    return <Dragger {...props} customRequest={({ onSuccess }) => {
      setTimeout(() => {
        onSuccess('ok');
      }, 0)
    }} />;
  };

  return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };
});

it("completes the image upload", async () => {
  const onUploadComplete = jest.fn();
  const flushPromises = () => new Promise(setImmediate);

  const { getByTestId } = render(
    <App onUploadComplete={onUploadComplete} />
  );

  const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });

  const uploadDragger = await waitFor(() => getByTestId("upload-dragger"));
  await act(async () => {
    userEvent.upload(uploadDragger, file);
  });
  await flushPromises();
  expect(onUploadComplete).toHaveBeenCalled();
});

不幸的是,我 couldn't set up 一个 codesandbox 显示它正在工作,但如果您遇到任何问题,请告诉我,我可以将代码推送到 repo。