I'm using jest+nock+jsdom modules to test my React\Redux application. I need to test this async action function:
export function updateUserPhoto (file, token) {
const data = new FormData()
data.append('file', file)
return dispatch => {
dispatch(userPutPhotoRequest())
return axios({
method: 'PUT',
headers: {
'x-access-token': token
},
data: data,
url: API_URL + '/user/photo'
})
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
}
}
So i'm using jsdom to provide FormData and File objects into tests:
const {JSDOM} = require('jsdom')
const jsdom = (new JSDOM(''))
global.window = jsdom.window
global.document = jsdom.window.document
global.FormData = jsdom.window.FormData
const File = jsdom.window.File
global.File = jsdom.window.File
And this is the method to test "upload photo" function:
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File([''], 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
const expectedFormData = new FormData()
expectedFormData.append('file', file)
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', expectedFormData)
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
Where i'm using nock to mock axios requests, redux-mock-store to mock Redux store. Creating File and FormData objects to compare it with response from axios. And then i'm calling action function passing file and token as parameters.
In production action function works and dispatch action success fine. But in testing i'm receiving error:
Error: Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream
When i pass into axios empty object as data test passes, so problem in FormData object. How can i mock FormData object for axios in an appropriate way to make this test work ?
This answer is coming way too late, but I was looking to do something similar and I wanted to post a solution here that someone else might stumble across and find useful.
The main problem here is that nock mocks network requests and not Javascript libraries. FormData
is a Javascript object that eventually gets transformed to text when making network requests. By the time the FormData
object makes it to nock, its been converted to a string
or a Buffer
, hence the error you see. nock is unable to use the FormData
object for comparison.
You have a few options:
Just don't match against the data in the PUT
request. The reason you are mocking is because you don't want a real HTTP request to go out but you want a fake response back. nock
only mocks the request once, so if you mock all PUT
requests to /user/photo
nock will catch it but only for that test:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo')
.reply(200, {body: {}})
Before you implement the test this way, think about what your test is trying to verify. Are you trying to verify that the file is sent in the HTTP request? If yes, then this is a poor option. Your code could send a completely different file than the one dispatched and still pass this test. If however you have another test to verify the file is being put in the HTTP request properly then this solution might save you some time.
If you do want the test to fail if your code passed a corrupted or wrong file, then he simplest solution would be to test for the filename. Since your file is empty there is no need to match the content, but we can match on the filename:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', /Content-Disposition\s*:\s*form-data\s*;\s*name="file"\s*;\s*filename="filename.txt"/i)
.reply(200, {body: {}})
This should match the simple case where you have one file uploading.
Say you have additional fields to be added to your request
export function updateUserPhoto (file, tags, token) {
const data = new FormData()
data.append('file', file)
data.append('tags', tags)
...
OR you have fake content in the file that you want to match on
const file = new File(Array.from('file contents'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
This is where things get a bit complex. Essentially what you need to do is to parse the form data text back into an object and then write your own matching logic.
parse-multipart-data
is a fairly simple parser that you could use:
https://www.npmjs.com/package/parse-multipart-data
Using that package your test might look something like this
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File(Array.from('file content'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', function (body) { /* You cannot use a fat-arrow function since we need to access the request headers */
// Multipart Data has a 'boundary' that works as a delimiter.
// You need to extract that
const boundary = this.headers['content-disposition']
.match(/boundary="([^"]+)"/)[1];
const parts = multipart.Parse(Buffer.from(body),boundary);
// return true to indicate a match
return parts[0].filename === 'filename.txt'
&& parts[0].type === 'text/plain'
&& parts[0].data.toString('utf8') === 'file contents'
&& parts[1].name === 'tags[]'
&& parts[1].data.toString('utf8') === 'tag1'
&& parts[2].name === 'tags[]'
&& parts[2].data.toString('utf8') === 'tag2';
})
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, ['tag1', 'tag2'], token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With