How to simulate uploading file via Ant Design's Upload using React Testing Library?
Asked Answered
A

3

6

I am using Ant Design's Upload component.

 <Upload 
     accept=".xlsx" 
     onChange={onImport}
  >
     <Button>Upload</Button>
 </Upload>

I have attempted to simulate the change using fireEvent.change which does nothing:

const inputEl = screen.getByText('Import');
const file = new File(['(⌐□_□)'], 'chucknorris.xlsx');
fireEvent.change(inputEl, { target: { files: [file] } });

I also tried to use fireEvent.drop. I tried setting both the dataTransfer and files properties.

Object.defineProperty(inputEl, 'dataTransfer', {
    value: {
        files: [file]
    }
});
Object.defineProperty(inputEl, 'files', {
    value: [file]
});
fireEvent.drop(inputEl);

This triggers the upload, but I keep getting the following error:

Cannot read property 'files' of undefined
  at AjaxUploader._this.onFileDrop (node_modules/rc-upload/lib/AjaxUploader.js:108:63)

How can I test Ant Design's Upload?

Asthenic answered 23/9, 2020 at 20:59 Comment(0)
S
4

Here is how I handled it. Since input is hidden and you can not query it with RTL I just queried the input using document like this:

async simulateFileUpload() {
  const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
  const hiddenFileInput = document.querySelector('input[type="file"]') as Element;

  await act(async () => {
    fireEvent.change(hiddenFileInput, { target: { files: [file] } });
  });
}

Not the prettiest thing to do, but it is the only way to test upload functionality.
I wrapped fireEvent in await act() but that is only because upload triggers some state changes in my application, you can just use throw it away of course

Stadium answered 26/1, 2021 at 22:21 Comment(0)
U
1

You can use user-event; is a companion library for Testing Library that provides more advanced simulation of browser interactions than the built-in fireEvent method.

Installation:

npm install --save-dev @testing-library/user-event

Usage:

import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('upload file', () => {
   const file = new File(['hello'], 'hello.png', { type: 'image/png' })

    render(
        <div>
           <label htmlFor="file-uploader">Upload file:</label>
           <input id="file-uploader" type="file" />
        </div>
    )
    const input = screen.getByLabelText(/upload file/i)
    userEvent.upload(input, file)

    expect(input.files[0]).toStrictEqual(file)
    expect(input.files.item(0)).toStrictEqual(file)
    expect(input.files).toHaveLength(1)
})
Uncinus answered 15/5, 2021 at 3:4 Comment(0)
T
0

A combination between the two answers, plus the handling of the requests is what has worked for me. In this example you don't provide a URL to do the POST request, but the Dragger component of Ant Design has this option available. I just want to share my solution for this question + an extra issue I found while using the action property, just to save you time.

To test the Dragger I did this:

test("Dragger functionality", async () => {
  const flushPromises = () => new Promise(setImmediate);
  const file = new File(["{test: 1}"], "test.json", {type: 'application/json'})
  const input = screen.getByTestId("uploader"); 
  // I added the data-testid property to the Dragger element, with the value "uploader"
  await act(async () => {
    fireEvent.change(input, {target: {files: [file]}});
  });
  await flushPromises();
});

The userEvent.upload method never worked for me. I've been using the userEvent for all the other tests but I couldn't make it work in this case.


Extra: If you are using the Dragger as I was

<Dragger multiple={true} accept={".json"} onChange={onChange} action={"/test"} data-testid={"uploader"}>

with the onChange and action properties, you will probably not get the onChange called. To make it work, you have to handle the POST request that is made, in this case to the /test. So, the complete test would be:

import { act, fireEvent, render, screen } from "@testing-library/react";
import { setupServer } from "msw/node";
import { rest } from "msw";
import React from "react";
import { Upload } from "antd";
const { Dragger } = Upload;

const onChangeMock = jest.fn();
  
const server = setupServer(rest.post("/test", (req, res, ctx) => {
  return res(ctx.json({response: "200 OK"}));
}));

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test("Dragger functionality", async () => {
  const flushPromises = () => new Promise(setImmediate);
  
  render (
    <Dragger multiple={true} accept={".json"} onChange={onChangeMock} action={"/test"} data-testid={"uploader"}>
      <p className="ant-upload-drag-icon">
        <InboxOutlined />
      </p>
      <p className="ant-upload-text">{"description"}</p>
      <p className="ant-upload-hint">{"hint"}</p>
    </Dragger>
  );
  
  const file = new File(["{test: 1}"], "test.json", {type: 'application/json'})
  const input = screen.getByTestId("uploader"); 
  // I added the data-testid property to the Dragger element, with the value "uploader"
  await act(async () => {
    fireEvent.change(input, {target: {files: [file]}});
  });
  await flushPromises();
  expect(onChangeMock).toBeCalled();
});

Hopefully this is useful for someone.

Timoshenko answered 20/10, 2021 at 0:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.