Jest 在 React 中测试异步图片上传
Jest Testing an asynchronous image upload in React
我正在尝试测试一组在用户上传文件后异步 运行 的函数。这些功能都在一个名为 UploadImageModal 的 React 组件中。这些函数由 this on change 函数触发:
const onChange = async () => {
try {
const file = document.querySelector('#upload-profile-img-dialog')
.files[0];
if (file) {
const fileData = {
mimeType: file.type,
base64: await getBase64(file),
file
};
await hasMinPicDimension(fileData);
setPicFile(fileData);
}
} catch (error_) {
setError(error_.message);
}
};
这个 on change 函数然后调用这个 hasMinPicDimensions 函数:
const hasMinPicDimension = ({ mimeType, base64, file }) =>
new Promise((resolve, reject) => {
const image = new Image();
image.src = `data:${mimeType};base64,${base64}`;
image.addEventListener('load', () => {
if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
return reject(new Error('Image is not a supported type'));
}
if (image.height >= 151 && image.width >= 151) {
return resolve({ mimeType, base64, file });
}
return reject(new Error('Image is too small'));
});
image.addEventListener('error', () => {
reject(new Error('File is not an image'));
});
});
在该函数解析它的 promise 之后,setPicFile,这是一个反应状态更改,它更改组件 src 以具有图像的数据 URI。
这是图片上传输入组件:
<Input
id="upload-profile-img-dialog"
name="profileImage"
type="file"
accept="image/gif,image/jpeg,image/png"
onChange={onChange}
sx={{ visibility: 'hidden' }}
data-testid="fileinput"
/>
我已经尝试通过仅使用 Jest 来模拟图像上传来测试此功能 - 但无论上面的功能是什么都不会触发,因为 Jest 不会等待承诺解决。
有人对我如何使用 Jest 执行此操作有任何建议吗?如果我也需要用enyzme就可以了。
我们测试组件,模拟组件依赖的其他模块和模块导出的函数。
例如
index.jsx
:
import React, { useState } from 'react';
import { getBase64, hasMinPicDimension } from './helpers';
export function UploadImageModal() {
const [error, setError] = useState('');
const [picFile, setPicFile] = useState();
const onChange = async () => {
try {
const file = document.querySelector('#upload-profile-img-dialog').files[0];
if (file) {
const fileData = {
mimeType: file.type,
base64: await getBase64(file),
file,
};
await hasMinPicDimension(fileData);
setPicFile(fileData);
}
} catch (error_) {
setError(error_.message);
}
};
if (error) return <p>{error}</p>;
// I use console.log to simulate using picFile
console.log('picFile: ', picFile);
return (
<input
id="upload-profile-img-dialog"
name="profileImage"
type="file"
accept="image/gif,image/jpeg,image/png"
onChange={onChange}
data-testid="fileinput"
/>
);
}
helpers.js
:
export const hasMinPicDimension = async ({ mimeType, base64, file }) =>
new Promise((resolve, reject) => {
const image = new Image();
image.src = `data:${mimeType};base64,${base64}`;
image.addEventListener('load', () => {
if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
return reject(new Error('Image is not a supported type'));
}
if (image.height >= 151 && image.width >= 151) {
return resolve({ mimeType, base64, file });
}
return reject(new Error('Image is too small'));
});
image.addEventListener('error', () => {
reject(new Error('File is not an image'));
});
});
export async function getBase64(file) {
console.log('Your getBase64 real implementation');
}
index.test.jsx
:
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { UploadImageModal } from './';
import { getBase64, hasMinPicDimension } from './helpers';
jest.mock('./helpers');
describe('68818166', () => {
afterEach(() => {
jest.restoreAllMocks();
});
afterAll(() => {
jest.resetAllMocks();
});
test('should upload file', async () => {
const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
const logSpy = jest.spyOn(console, 'log');
getBase64.mockResolvedValueOnce('mocked file base64 string');
hasMinPicDimension.mockImplementationOnce((file) => file);
render(<UploadImageModal />);
fireEvent.change(screen.queryByTestId('fileinput'));
await waitFor(() =>
expect(logSpy).toBeCalledWith('picFile: ', {
mimeType: 'image/png',
base64: 'mocked file base64 string',
file,
})
);
});
test('should handle error if file is invalid', async () => {
const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
getBase64.mockResolvedValueOnce('mocked file base64 string');
hasMinPicDimension.mockRejectedValueOnce(new Error('Image is too small'));
render(<UploadImageModal />);
await act(async () => {
fireEvent.change(screen.queryByTestId('fileinput'));
});
await waitFor(() => {
expect(screen.findByText(/Image is too small/)).toBeTruthy();
});
});
});
测试结果:
console.log
picFile: undefined
at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
console.log
picFile: {
mimeType: 'image/png',
base64: 'mocked file base64 string',
file: File {}
}
at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
console.log
picFile: undefined
at UploadImageModal (examples/68818166/index.jsx:29:11)
PASS examples/68818166/index.test.jsx
68818166
✓ should upload file (105 ms)
✓ should handle error if file is invalid (10 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 61.76 | 30 | 33.33 | 60 |
helpers.js | 18.75 | 0 | 0 | 14.29 | 2-18,23
index.jsx | 100 | 75 | 100 | 100 | 11
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.409 s, estimated 10 s
我正在尝试测试一组在用户上传文件后异步 运行 的函数。这些功能都在一个名为 UploadImageModal 的 React 组件中。这些函数由 this on change 函数触发:
const onChange = async () => {
try {
const file = document.querySelector('#upload-profile-img-dialog')
.files[0];
if (file) {
const fileData = {
mimeType: file.type,
base64: await getBase64(file),
file
};
await hasMinPicDimension(fileData);
setPicFile(fileData);
}
} catch (error_) {
setError(error_.message);
}
};
这个 on change 函数然后调用这个 hasMinPicDimensions 函数:
const hasMinPicDimension = ({ mimeType, base64, file }) =>
new Promise((resolve, reject) => {
const image = new Image();
image.src = `data:${mimeType};base64,${base64}`;
image.addEventListener('load', () => {
if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
return reject(new Error('Image is not a supported type'));
}
if (image.height >= 151 && image.width >= 151) {
return resolve({ mimeType, base64, file });
}
return reject(new Error('Image is too small'));
});
image.addEventListener('error', () => {
reject(new Error('File is not an image'));
});
});
在该函数解析它的 promise 之后,setPicFile,这是一个反应状态更改,它更改组件 src 以具有图像的数据 URI。 这是图片上传输入组件:
<Input
id="upload-profile-img-dialog"
name="profileImage"
type="file"
accept="image/gif,image/jpeg,image/png"
onChange={onChange}
sx={{ visibility: 'hidden' }}
data-testid="fileinput"
/>
我已经尝试通过仅使用 Jest 来模拟图像上传来测试此功能 - 但无论上面的功能是什么都不会触发,因为 Jest 不会等待承诺解决。
有人对我如何使用 Jest 执行此操作有任何建议吗?如果我也需要用enyzme就可以了。
我们测试组件,模拟组件依赖的其他模块和模块导出的函数。
例如
index.jsx
:
import React, { useState } from 'react';
import { getBase64, hasMinPicDimension } from './helpers';
export function UploadImageModal() {
const [error, setError] = useState('');
const [picFile, setPicFile] = useState();
const onChange = async () => {
try {
const file = document.querySelector('#upload-profile-img-dialog').files[0];
if (file) {
const fileData = {
mimeType: file.type,
base64: await getBase64(file),
file,
};
await hasMinPicDimension(fileData);
setPicFile(fileData);
}
} catch (error_) {
setError(error_.message);
}
};
if (error) return <p>{error}</p>;
// I use console.log to simulate using picFile
console.log('picFile: ', picFile);
return (
<input
id="upload-profile-img-dialog"
name="profileImage"
type="file"
accept="image/gif,image/jpeg,image/png"
onChange={onChange}
data-testid="fileinput"
/>
);
}
helpers.js
:
export const hasMinPicDimension = async ({ mimeType, base64, file }) =>
new Promise((resolve, reject) => {
const image = new Image();
image.src = `data:${mimeType};base64,${base64}`;
image.addEventListener('load', () => {
if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
return reject(new Error('Image is not a supported type'));
}
if (image.height >= 151 && image.width >= 151) {
return resolve({ mimeType, base64, file });
}
return reject(new Error('Image is too small'));
});
image.addEventListener('error', () => {
reject(new Error('File is not an image'));
});
});
export async function getBase64(file) {
console.log('Your getBase64 real implementation');
}
index.test.jsx
:
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { UploadImageModal } from './';
import { getBase64, hasMinPicDimension } from './helpers';
jest.mock('./helpers');
describe('68818166', () => {
afterEach(() => {
jest.restoreAllMocks();
});
afterAll(() => {
jest.resetAllMocks();
});
test('should upload file', async () => {
const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
const logSpy = jest.spyOn(console, 'log');
getBase64.mockResolvedValueOnce('mocked file base64 string');
hasMinPicDimension.mockImplementationOnce((file) => file);
render(<UploadImageModal />);
fireEvent.change(screen.queryByTestId('fileinput'));
await waitFor(() =>
expect(logSpy).toBeCalledWith('picFile: ', {
mimeType: 'image/png',
base64: 'mocked file base64 string',
file,
})
);
});
test('should handle error if file is invalid', async () => {
const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
getBase64.mockResolvedValueOnce('mocked file base64 string');
hasMinPicDimension.mockRejectedValueOnce(new Error('Image is too small'));
render(<UploadImageModal />);
await act(async () => {
fireEvent.change(screen.queryByTestId('fileinput'));
});
await waitFor(() => {
expect(screen.findByText(/Image is too small/)).toBeTruthy();
});
});
});
测试结果:
console.log
picFile: undefined
at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
console.log
picFile: {
mimeType: 'image/png',
base64: 'mocked file base64 string',
file: File {}
}
at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
console.log
picFile: undefined
at UploadImageModal (examples/68818166/index.jsx:29:11)
PASS examples/68818166/index.test.jsx
68818166
✓ should upload file (105 ms)
✓ should handle error if file is invalid (10 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 61.76 | 30 | 33.33 | 60 |
helpers.js | 18.75 | 0 | 0 | 14.29 | 2-18,23
index.jsx | 100 | 75 | 100 | 100 | 11
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.409 s, estimated 10 s