React-testing-library: Simulating uploading file

Created on 25 May 2018  ·  31Comments  ·  Source: testing-library/react-testing-library

Is there a way to simulate or fire event to trigger change event on input that is used for uploading files?

Most helpful comment

Here's what I've got for that test:

test('can select an image and upload will make a request to upload it', async () => {
  const { container, getByLabelText, getByText, getByAltText } = render(
    <ProfilePhoto />,
  )
  const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' })
  const imageInput = getByLabelText('choose image')
  Simulate.change(imageInput, { target: { files: [file] } })

  await wait(() => getByAltText('image-preview'))
  const dataURL = getByAltText('image-preview').src
  expect(dataURL).toMatchSnapshot(
    'data url in the image-preview src for this string: "(⌐□_□)"',
  )

  // ensure the form is submittable
  expect(getByText('Upload Image').type).toBe('submit')

  // submit the form
  Simulate.submit(container.querySelector('form'))
  expect(mockClient.request).toHaveBeenCalledTimes(1)
  expect(mockClient.request).toHaveBeenCalledWith(expect.any(String), {
    dataURL,
  })
})

All 31 comments

I think it should be possible using fireEvent.

Have you tried something like this below:

import React from 'react'
import {renderIntoDocument, fireEvent} from 'react-testing-library'

test('some test', () => {
  const {getByLabelText} = renderIntoDocument(
    <div>
      <label htmlFor="avatar">Avatar</label>
      <input id="avatar" type="file" />
    </div>
  )
  const fileInputField = getByLabelText('Avatar')
  const event = {
    target: {
      value: '/not/sure/what/goes/in/here',
    },
  }
  fireEvent.change(fileInputField, event)
  // expect here things that should have happened when the file input changed
})

Never tried it myself, and can't try it now, but I'd assume is similar to a regular text input field change event. You should pass in the event object above something that resembles the relevant parts of the actual event object that would be passed in a real situation in the browser.

Hey @gnapse,

I've tried your approach - I don't get any runtime errors but this is not triggering onChange event on react side. Is that the case also for You?Any ideas why this is happening?

One thing I can add, with enzyme simulate everything seems to be alright:

wrapper.find('#files').simulate('change', {
      target: {
         files: [
           'dummyValue.something'
         ]   
      }
    });

I think you're right. I was able to create an example that reproduces the problem: https://codesandbox.io/s/yp6y4ynlqx

If the input is type="text" the spy function is called. By simply changing it to type="file" the tests do not pass anymore. I changed it to type="email" and it works too (understandably, that type is particular kind of text). It looks like there's something special about file inputs.

I think this is actually an issue for dom-testing-library but I'll dig more into it in the mean time, before asking you to open the issue there.

Hi! I've done this in a work project and I remember I had to do some slightly odd stuff to make it work. Tomorrow I'll be able to reference what I did and I'll give an example here.

Here's what I've got for that test:

test('can select an image and upload will make a request to upload it', async () => {
  const { container, getByLabelText, getByText, getByAltText } = render(
    <ProfilePhoto />,
  )
  const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' })
  const imageInput = getByLabelText('choose image')
  Simulate.change(imageInput, { target: { files: [file] } })

  await wait(() => getByAltText('image-preview'))
  const dataURL = getByAltText('image-preview').src
  expect(dataURL).toMatchSnapshot(
    'data url in the image-preview src for this string: "(⌐□_□)"',
  )

  // ensure the form is submittable
  expect(getByText('Upload Image').type).toBe('submit')

  // submit the form
  Simulate.submit(container.querySelector('form'))
  expect(mockClient.request).toHaveBeenCalledTimes(1)
  expect(mockClient.request).toHaveBeenCalledWith(expect.any(String), {
    dataURL,
  })
})

Because this is neither a bug report or a feature request, I'm going to go ahead and close this. Please feel free to ask questions in the spectrum community: https://spectrum.chat/react-testing-library

Heyya @kentcdodds

I was trying out the method you suggested above. But I had a problem in the actual code handler.

It somewhat looks like this

  const handleImageUpload = e => {
    console.log(e.target.files[0]) // undefined while testing
    this.onRequestImageUpload(e.target.files[0]) // this works fine in browser
  }

So, I had to do this in test

test('<UserAvatar /> | Uploading an image should open a modal', () => {
  const {getByLabelText} = render(<UserAvatar name="Arijit" />)

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

  // i have to do this because `input.files =[file]` is not allowed
  Object.defineProperty(inputEl, 'files', {
    value: [file]
  })

  fireEvent.change(inputEl)

  // check for modal now
})

I figured this out from jsdom/jsdom#1272

Can you please confirm this? Can your handler just reference files[0] while testing.

Again thanks a lot. You are awesome.

Hi @hoodwink73, so are you saying what you have there is _working_? Or is it not? Are you just asking if there's a way to get around the defineProperty thing? If that's all you're asking, my answer is I don't know, but probably not. I wouldn't worry about it though. Good luck!

The defineProperty thing is working. And yes, I guess I can live with it. 😊

My question was, did this Simulate.change(imageInput, { target: { files: [file] } }) work for you?

It did work for me. Now Simulate is removed though and I haven't had a chance to try the fireEvent version on my codebase but I'm pretty sure it'll work the same way.

@kentcdodds I followed what's written here, and in my test I do this:

const file = new File(['dummy content'], 'example.png', {type: 'image/png'})
const imageInput = getByLabelText('Select an image')
fireEvent.change(imageInput, {target: {files: [file]}})

But in my component

selectFile = e => {
  // e.target.files[0] is undefined
  this.setState({file: e.target.files[0]})
}

Works nicely in the browser though.

Might that :point_down: be the issue?

If you want to trigger the onChange handler of a controlled component with a different event.target.value, sending value through eventProperties won't work like it does with Simulate.

That's probably the problem. Perhaps you can try:

const file = new File(['dummy content'], 'example.png', {type: 'image/png'})
const imageInput = getByLabelText('Select an image')
imageInput.files = [file]
fireEvent.change(imageInput)

I haven't updated my implementation to the latest version yet so I haven't tried this. Good luck!

That throws TypeError: Failed to set the 'files' property on 'HTMLInputElement': The provided value is not of type 'FileList'..
From what I have found, files is a read only property on an input of type file. It also look likeFileList` is a read only object that can't be easily created.

I have reverted to version 3 to use Simulate for now.

Hey, @krzystof

Try something like this

test('<UserAvatar /> | Uploading an image should open a modal', () => {
  const {getByLabelText} = render(<UserAvatar name="Arijit" />)

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

  // i have to do this because `input.files =[file]` is not allowed
  Object.defineProperty(inputEl, 'files', {
    value: [file]
  })

  fireEvent.change(inputEl)

  // check for modal now
})

Once we find a solid solution we should definitely add it to the examples!

Here is a small example

Damn! Sorry @hoodwink73, I saw your solution before posting, tried it, but I think I did something wrong...
Tried it again, and that works well indeed. Thanks a lot for your help!

Sweet! Who would like to make the pull request to add this example to the examples codesandbox/repo?

✋ I will

Gots some great advice from this thread, but the fireEvent.change didn't work for me so I wanted to share that instead fireEvent.drop works for me. Also, I am using 'react-dropzone'.

describe('Uploader', () => {
  test('uploads file', async () => {
    const rows = ['NAME,ADDRESS,ZIP', 'james,1800 sunny ln,40000', 'ronda,1200 peaches ln,50000']
    const file = new File([rows.join('\n')], 'some.csv')
    const { getByText } = render(<Uploader/>)
    const formElement = getByText('Drop File');
    Object.defineProperty(formElement, 'files', { value: [file] });
    fireEvent.drop(formElement)
    await waitForElement(() => getByText('Success'))

    expect(getByText('Your file has been uploaded!')).toBeDefined()
  })
})

I will

Here is a small example

Thank you so much for the example @hoodwink73

I ran into the same issue with some code using react-hook-form and the above solutions didn't work out for me. After some time digging into the lib, it appeared that the validator was based on the html input value property and not its files property. Setting both got me of the hook.
See https://github.com/react-hook-form/react-hook-form/issues/685 for details.

Hi everyone, just ran into this issue today while developing some code and here's how I've managed to make it work as expected since none of the above where working for me!

test('To call function props for uploading & uploaded', async () => {
  const mockUpload = jest.fn(
    (base64: string): Promise<string> => {
      return new Promise((resolve) => resolve(base64))
    }
  )
  const mockOnUpload = jest.fn((): void => {
    // Do nothing
  })

  const { getByTestId } = render(
    <AvatarUpload upload={mockUpload} onUpload={mockOnUpload} />
  )

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

  // Source: https://github.com/testing-library/react-testing-library/issues/93#issuecomment-403887769
  Object.defineProperty(upload, 'files', {
    value: [file]
  })

  fireEvent.change(upload)

  const preview = await waitForElement(() => getByTestId('preview'))

  expect(mockUpload).toBeCalled()
  expect(mockOnUpload).toBeCalled()
  expect(preview).toBeInTheDocument()
})

Hope it works for y'all 👍

Just incase someone was running into the same as me:

If you are using the onInput event handler:

<input
  type="file"
  accept=".json"
  onInput={handleImportProfile}
/>

You also need to trigger an input event, not just change:

fireEvent.change(inputEl, { target: { files: [file] }})
fireEvent.input(inputEl)

or

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

Here's what I've got for that test:

test('can select an image and upload will make a request to upload it', async () => {
  const { container, getByLabelText, getByText, getByAltText } = render(
    <ProfilePhoto />,
  )
  const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' })
  const imageInput = getByLabelText('choose image')
  Simulate.change(imageInput, { target: { files: [file] } })

  await wait(() => getByAltText('image-preview'))
  const dataURL = getByAltText('image-preview').src
  expect(dataURL).toMatchSnapshot(
    'data url in the image-preview src for this string: "(⌐□_□)"',
  )

  // ensure the form is submittable
  expect(getByText('Upload Image').type).toBe('submit')

  // submit the form
  Simulate.submit(container.querySelector('form'))
  expect(mockClient.request).toHaveBeenCalledTimes(1)
  expect(mockClient.request).toHaveBeenCalledWith(expect.any(String), {
    dataURL,
  })
})

Worked for me!

Now you can use upload function from @testing-library/user-event

@vadimshvetsov if possible, could you show a very small working example? because I have tried using upload from user-event, but not working. Probably I am missing something.

@luane-aquino yeah, it may look like this:

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

test("should upload the file", () => {
  const file = new File(["hello"], "hello.png", { type: "image/png" })
  render(<input type="file" data-testid="element" />)

  const input = screen.getByTestId("element")
  user.upload(input, file)

  expect(input.files[0]).toStrictEqual(file)
  expect(input.files).toHaveLength(1)
})

Thanks @vadimshvetsov . I had seen this example in the docs. But what I meant was a very simple example/project that I could download and run on my computer.

@luane-aquino then you could check this CodeSandbox example. You may take this example to your github repo and run locally afterwards.

Hi there, are you able to provide examples on how to initialize click or change events in the third parameter upload(element, file, [{ clickInit, changeInit }]) please? And what sort of use cases would this help?

And does this conflict with FireEvent that has been mentioned above?

Was this page helpful?
0 / 5 - 0 ratings