Mocking File Upload in Vue with Jest
Testing your code is important, but it can also be time-consuming.
In my experience, when it comes to testing Vue with Jest, half the battle is figuring out how to mock something. Sometimes, I can spend 1-hour building a feature and 2+ hours writing tests for that feature. Mocking file upload is a perfect example of that, although it admittedly took me much longer than 2-hours to finally figure it out.
Vue Test Utils is a great package for Vue testing, and it provides many helper methods for mocking the functionality of different HTML form elements, but an input of type file
is unfortunately not included. In this post, we’ll discuss how to combine Vue Test Utils helpers with Jest helpers to mock and test file upload in Vue (and hopefully spare you hours of frustration).
Setup
We’ll be focusing on image upload in this example, but the general principles should be applicable to other file types. First, we have a form
element with an input
of type file:
<form
method="POST"
enctype="multipart/form-data"
>
<input
@change="onChange"
type="file"
accept="image/*"
>
</form>
This form contains a single input that will accept images of any type, and the form will be submitting FormData
, as designated by enctype=”multipart/form-data”
.
When a file is uploaded, the onChange
method will be triggered:
onChange(event) {
if (!event.target.files.length) { return }
const image = event.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(image)
this.persist(image)
},
persist(image) {
const data = new FormData()
data.append('image', image)
// Send the image to the API (e.g., with a Vuex action)
},
In the onChange
method, we’re retrieving the image from the event and converting it to a base64 encoded string so that it can be attached as FormData
in the persist
method. Note that the first argument of data.append()
needs to match the property name that your API is expecting.
But how do we test this?
To test this with Jest, we’ll need to mock 2 things:
- The
event
- The
FileReader.readAsDataURL()
method
// profile-image-form.spec.js
import { mount } from '@vue/test-utils'
import ProfileImageForm from '@/components/ProfileImageForm'
const event = {
target: {
files: [
{
name: 'image.png',
size: 50000,
type: 'image/png',
},
],
},
}
describe('ProfileImageForm', () => {
it('makes a call to persist the image on image upload', () => {
// Mount the component
const wrapper = mount(ProfileImageForm)
// Mock FileReader.readAsDataURL() to be a function that returns null
const fileReaderSpy = jest.spyOn(FileReader.prototype, 'readAsDataURL').mockImplementation(() => null)
// Spy on the component’s persist() method
const persistSpy = jest.spyOn(wrapper.vm, 'persist')
// Manually trigger the component’s onChange() method
wrapper.vm.onChange(event)
// Assert that the FileReader object was called with the uploaded image
expect(fileReaderSpy).toHaveBeenCalledWith(event.target.files[0])
// Assert that the component’s persist() method was called with the uploaded image
expect(persistSpy).toHaveBeenCalledWith(event.target.files[0])
})
}
What’s going on here?
First, we need to create an event
object that mimics the structure of an actual image upload event. Next, we need to mount the component using the mount
method provided by Vue Test Utils.
Now comes the fun part. We mock FileReader.readAsDataURL()
by spying on the FileReader
object, specifically upon its prototype
because that’s where the object’s methods are located. We then specify the exact method with the ‘readAsDataURL’
argument. If we only provide FileReader
as the first argument, we’ll get an error that the readAsDataURL
property is not a function.
Simply spying on the method won’t help us, though, because it won’t change how the method works. We also need to mock its implementation, which is what we do with .mockImplementation(() => null)
. In this case, we’re saying that FileReader.readAsDataURL()
should be a method that returns null
. We’re having the method return null
because we don’t actually need to convert our fake event into a base64 encoded string; we just need to check that the method was called with the uploaded image.
Our other spy (persistSpy)
is created so we can assert that the component’s persist
method was called.
Finally, all that’s left to do is trigger the component’s onChange
method. The method has to be triggered manually because we can’t actually upload a file in our test, and we also don’t need to spend time testing the built-in functionality of the browser. Once the method has been triggered, we can assert that the readAsDataURL
and persist
methods were called with the uploaded image.
Testing Vue components with Jest can be complex, but it doesn’t have to be. When you come across a built-in method, like FileReader.readAsDataURL()
, that requires a very specific input that you can’t provide in a test, all you have to do is mock that method’s implementation. In doing so, you’ll prevent it from trying to do all the things it normally does behind the scenes and be able to test what you really need to test — that the method was called with a specific argument.
If you have other ways of testing Vue file upload, let us know @zaengle. We’d love to hear about them.
Want to read more tips and insights on working with a Vue development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
Engineer
What happens when you cross years of study in biology and medicine with a degree in computer science? You get someone like Rachel.