react-testing-library - how to simulate file upload to a <input type="file" /> element?
Asked Answered
I

7

71

I am using user-event to try to have more 'realistic' user interactions. However, after I click on the input, it will not fire the onChange function because by default it would only bring up the file explorer for the user to upload a file. How do I simulate the user uploading a file?

My code:

// Component
const FileInputComponent = ({ handleFileUpload }) => (
  <div>
    <input type="file" id="testing" accept=".png,.jpg" onChange={handleFileUpload} />
    <label htmlFor="testing">Input File Here</label>
  </div>
);
// Test file
test("Clicking on the label button calls the `handleFileUpload` function", () => {
  const handleFileUploadMockFn = jest.fn();
  const { getByLabelText } = render(<FileInputComponent handleFileUpload={handleFileUploadMockFn} />
  userEvent.click(getByLabelText("Input File Here"));
  expect(handleFileUploadMockFn).toHaveBeenCalledTimes(1);
});
Infarction answered 8/4, 2020 at 15:53 Comment(0)
S
32

Is the upload you want to test? https://github.com/testing-library/user-event

I just write

const fileInput = getByLabelText('file-input-label')
userEvent.upload(fileInput, testFile)

and it simulates the onChange for me.

Sheffy answered 7/12, 2020 at 21:55 Comment(0)
P
32

I found a bit of a hacky solution and I'm not sure if it matches the best practices in testing but I will share it with you it might help.

describe("Upload files", () => {
  let file;

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

  test("cover photo upload", async () => {
    // render the component
    const { getByTestId } = render(<YourComponent />);

    // get the upload button
    let uploader = getByTestId("photo-uploader");

    // simulate upload event and wait until finish
    await waitFor(() =>
      fireEvent.change(uploader, {
        target: { files: [file] },
      })
    );
    // get the same uploader from the dom
    let image = document.getElementById("photo-uploader");
    // check if the file is there
    expect(image.files[0].name).toBe("chucknorris.png");
    expect(image.files.length).toBe(1);
  });
});
Protolanguage answered 7/1, 2021 at 15:33 Comment(1)
import { fireEvent, waitFor } from '@testing-library/react';Epner
I
14

The answer by @darthzeren is nearly correct but the link seems outdated. Here is the solution described by lib maintainers: https://testing-library.com/docs/ecosystem-user-event/#uploadelement-file--clickinit-changeinit--options

Just to quote example from docs, in case the link does not work in the future:

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

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

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

  expect(input.files).toHaveLength(2)
  expect(input.files[0]).toStrictEqual(files[0])
  expect(input.files[1]).toStrictEqual(files[1])
})
Infrequency answered 11/8, 2021 at 19:1 Comment(1)
If you're using user-event@14 then you'll want to use the latest setup: testing-library.com/docs/user-event/utility/#uploadRegeniaregensburg
D
5

This is how we test a file input with RTL :

    describe('input file', () => {
      let rtl: RenderResult;

      beforeEach(() => {
        rtl = render(<MyComponentWithFileInput />);
      });

      test('input file', async () => {
        const file = new File(['(⌐□_□)'], 'file.xml', { type: 'application/xml' });

        // upload the file by updating the value attribute of the input
        // I assume : <input type="file" data-testid="fileInput" />
        fireEvent.change(rtl.getByTestId('fileInput'), {
          target: { files: [file] },
        });

        // here your onChange function should have been called
        expect(handleFileUploadMockFn).toHaveBeenCalledTimes(1);

        // if you have a form which is submitted you should be able to check values :
        expect(onSubmitForm).toHaveBeenCalledWith({
          file: expect.any(Object),
        });
      });
    });

This is the general idea, obviously this should be updated to match your setup.

Darien answered 26/11, 2020 at 8:51 Comment(1)
the fireEvent.change( section is the part that helped meWaterer
U
5

Input Element:

<input type="file" data-testid="fileDropzone"/>

First, render the element, then get the input file upload element by getByTestId.

Secondly, define a fake file:

const fakeFile = new File(['hello'], 'hello.png', { type: 'image/png' });

Then, import userEvent. and upload the file, make sure to rap inside act():

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

The test the expectation, here is the final code:

it('Upload Files', async () => {
  const { getByTestId } = render(<FileUplaodComponent/>);
  const fakeFile = new File(['hello'], 'hello.png', { type: 'image/png' });
  const inputFile = getByTestId(/fileDropzone/i);
  await act(async () => {
    await waitFor(() => {
      userEvent.upload(inputFile, fakeFile);
    });
  });

  expect(inputFile.files[0]).toStrictEqual(inputFile);
});
Unchurch answered 5/1, 2022 at 10:13 Comment(0)
S
5

The key that I found in the doc that I was surprised to not find here is:

await userEvent.upload(input, file)

No need to wrap in waitFor() or act()

https://testing-library.com/docs/user-event/utility/#upload

Saida answered 24/3, 2023 at 15:32 Comment(1)
This is the best answer. The #upload method returns a promise and needs to be awaitedUrsala
F
0

For me JacekLab's answer worked best, but I needed to make some adaptations for TypeScript like so:

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>,
  )
  // use 'as Type'
  const input = screen.getByLabelText(/upload file/i) as HTMLInputElement
  userEvent.upload(input, file)

  // Non-null assertion operator '!' removes 'null | undefined' from the type
  expect(input.files![0]).toStrictEqual(file)
  expect(input.files!.item(0)).toStrictEqual(file)
  expect(input.files).toHaveLength(1)
})
Fite answered 12/10, 2022 at 7:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.