9

I have the following react code in my project

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>

And I want to test if the callback function onUploadComplete() was called when file.status is 'done'.

Here is how i am doing the test right now:

  1. I have a jest.mock to simulate a dumb request that will always succeed
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. The test itself (same file as the mock), where it renders the component (where antd will be imported), simulate an upload and then checks if the callback has been called.
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. elementRenderer
const elementRenderer = ({
  onUploadComplete = () => {}
}) => (
  <ApplicationProvider>
    <UploadCompanyLogo
      onUploadComplete={onUploadComplete}
    />
  </ApplicationProvider>
);
  1. ApplicationProvider
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;

This is currently not working, but have already been improved with the help of @diedu.

The console.log() I have put in the MockedDragger it's currently not showing. If I put a console.log() in both component and mockedDragger, it prints the component log.

Any tips on how to proceed? I have already seen this issue and didn’t help.

  • What will happen when onUploadComplete() is called? I think it would be a success message right? I would recommend you to test that success message. – Subrato Pattanaik Jun 12 '21 at 11:02
  • onUploadComplete will trigger some database stuff, nothing that is very important to the test. I tried logging some messages and it works fine on the browser (when status is 'error' or 'uploading', since it never really completes the upload. I want to simulate the 'done' status to make sure this callback is always called – Gabriel Michelassi Jun 13 '21 at 13:03

1 Answers1

4

The first thing you should change is the return you are doing in your mock. You are returning a new component called MockedDragger, not Dragger.

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

The next thing is the event firing. According to this issue you should use RTL user-event library and wrap the call in act

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

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

and finally, due to some asynchronous code running you need to flush the pending promises before checking the call

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

this is the full version

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

Unfortunately, I couldn't set up a codesandbox to show it's working, but let me know if you face any issue and I can push the code to a repo.

diedu
  • 19,277
  • 4
  • 32
  • 49
  • Thank you very much for answering! But unfortunately still not working :(. I always get `Received number of calls: 0`. Do you need more info? My dragger is inside of some divs and has others props, such as beforeUpload and so on. – Gabriel Michelassi Jun 14 '21 at 03:33
  • Also, I don’t know how much this is useful, but if I insert a `console.log` inside the `jest.mock`'s callback, it never logs the message. Could it be something about the mock not working? Like, it's not mocking the Dragger for real. Just a hypothesis.. – Gabriel Michelassi Jun 14 '21 at 03:38
  • 1
    @GabrielMichelassi I actually put the `console.log` in the mock and I see it when running the test, please checkout this repo to see what could be different from your actual code https://github.com/diedu89/antd-dragger-test – diedu Jun 14 '21 at 03:45
  • Thank you for the repo, it's very useful for me, but I still can't find why the mock does not work for me. Appreciate the effort tho, I'll keep looking and If i got news I'll post here – Gabriel Michelassi Jun 14 '21 at 18:40
  • @GabrielMichelassi could you update your answer with the code you have now? I can help to identify what could be different – diedu Jun 14 '21 at 18:44
  • done! if you need any other change I'll make it – Gabriel Michelassi Jun 14 '21 at 19:11
  • the only difference I see is that you're using the `elementRenderer` function instead of rendering the component in the test, is that function from another file? maybe that's why is not getting the mock – diedu Jun 14 '21 at 19:37
  • Added the `elementRenderer` and also the `ApplicationProvider` that's used on it – Gabriel Michelassi Jun 14 '21 at 19:42
  • is that function in the same file as the test? – diedu Jun 14 '21 at 19:55
  • yes, it is... it's placed after the `imports` and before the `jest.mock()` thing – Gabriel Michelassi Jun 14 '21 at 19:57
  • ok, I'll try later to set a similar configuration on my code to see if that's the problem – diedu Jun 14 '21 at 19:59
  • Thank you, really appreciate – Gabriel Michelassi Jun 14 '21 at 19:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/233770/discussion-between-diedu-and-gabriel-michelassi). – diedu Jun 15 '21 at 02:45