Redux 使用 jest、nock、axios 和 jsdom 测试 multipart/form-data
Posted
技术标签:
【中文标题】Redux 使用 jest、nock、axios 和 jsdom 测试 multipart/form-data【英文标题】:Redux testing multipart/form-data with jest, nock, axios and jsdom 【发布时间】:2018-03-30 20:00:38 【问题描述】:我正在使用 jest+nock+jsdom 模块来测试我的 React\Redux 应用程序。 我需要测试这个异步操作函数:
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)))
所以我使用 jsdom 将 FormData 和 File 对象提供给测试:
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
这是测试“上传照片”功能的方法:
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)
)
)
我使用 nock 来模拟 axios 请求,使用 redux-mock-store 来模拟 Redux 存储。 创建 File 和 FormData 对象以将其与来自 axios 的响应进行比较。 然后我调用动作函数将文件和令牌作为参数传递。
在生产动作功能工作和调度动作成功正常。但在测试中我收到错误:
Error: Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream
当我在数据测试通过时传入 axios 空对象时,FormData 对象出现问题。 如何以适当的方式模拟 axios 的 FormData 对象以使该测试正常工作?
【问题讨论】:
你发现了吗?我正在尝试做类似的事情,但我得到了FormData is not a constructor
。还需要以某种方式模拟 FormData。
不幸的是我没有找到解决这个问题的方法。所以我能做的就是评论这个测试。
【参考方案1】:
这个答案来得太晚了,但我想做类似的事情,我想在这里发布一个解决方案,其他人可能会偶然发现并发现有用。
这里的主要问题是 nock 模拟网络请求而不是 javascript 库。 FormData
是一个 Javascript 对象,在发出网络请求时最终会转换为文本。当FormData
对象使其变为nock 时,它已被转换为string
或Buffer
,因此您会看到错误。 nock 无法使用FormData
对象进行比较。
你有几个选择:
1。最简单的解决方案
只是不要匹配PUT
请求中的数据。您嘲笑的原因是因为您不希望发出真正的 HTTP 请求,但希望返回虚假的响应。 nock
只模拟一次请求,所以如果你模拟所有PUT
对/user/photo
的请求,nock 会捕获它,但仅用于该测试:
nock(API_URL,
reqheaders:
'x-access-token': token
).put('/user/photo')
.reply(200, body: )
在以这种方式实施测试之前,请考虑一下您的测试试图验证什么。您是否尝试验证文件是否在 HTTP 请求中发送?如果是,那么这是一个糟糕的选择。您的代码可以发送与已调度的文件完全不同的文件,但仍能通过此测试。但是,如果您有另一个测试来验证文件是否正确放入 HTTP 请求中,那么此解决方案可能会为您节省一些时间。
2。在不匹配请求时让 nock 失败的简单解决方案
如果您的代码通过了损坏或错误的文件,您确实希望测试失败,那么最简单的解决方案是测试文件名。由于你的文件是空的,所以不需要匹配内容,但我们可以匹配文件名:
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: )
这应该与您上传一个文件的简单情况相匹配。
3。匹配表单数据字段的内容
假设您有其他字段要添加到您的请求中
export function updateUserPhoto (file, tags, token)
const data = new FormData()
data.append('file', file)
data.append('tags', tags)
...
或者您想要匹配的文件中有虚假内容
const file = new File(Array.from('file contents'), 'filename.txt',
type: 'text/plain',
lastModified: new Date()
)
这就是事情变得有点复杂的地方。本质上你需要做的是将表单数据文本解析回一个对象,然后编写你自己的匹配逻辑。
parse-multipart-data
是一个相当简单的解析器,您可以使用:
https://www.npmjs.com/package/parse-multipart-data
使用该包,您的测试可能看起来像这样
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)
)
)
【讨论】:
【参考方案2】:我正在处理同样的问题,问题是 axios 将 http 设置为默认适配器。而 xhr 正是您所需要的。
// axios/lib/defaults.js
function getDefaultAdapter()
var adapter;
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]')
// For node use HTTP adapter
adapter = require('./adapters/http');
else if (typeof XMLHttpRequest !== 'undefined')
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
return adapter;
因此,在 axios 调用上显式设置 xhr 适配器对我有用。
类似:
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
,
adapter: require('axios/lib/adapters/xhr'),
data: data,
url: API_URL + '/user/photo'
)
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
另外,我遇到了 nock 和 CORS 的问题,所以,如果你有同样的问题,你可以添加 access-control-allow-origin 标头
nock(API_URL,
reqheaders:
'x-access-token': token
)
.defaultReplyHeaders( 'access-control-allow-origin': '*' )
.put('/user/photo', expectedFormData)
.reply(200, body: )
【讨论】:
以上是关于Redux 使用 jest、nock、axios 和 jsdom 测试 multipart/form-data的主要内容,如果未能解决你的问题,请参考以下文章
使用 JEST 和 nock 进行测试导致“禁止跨源 null”