模拟测试使用 node-fetch 下载文件的功能?
此答案提供了以下使用 node-fetch 库下载文件的方法 https://stackoverflow.com/a/51302466 :
const downloadFile = (async (url, path) => {
const res = await fetch(url);
const fileStream = fs.createWriteStream(path);
await new Promise((resolve, reject) => {
res.body.pipe(fileStream);
res.body.on("error", reject);
fileStream.on("finish", resolve);
});
});
如果函数包含此方法,如何模拟此方法?我遇到了奇怪的问题:
unit.test.js
import { PassThrough } from 'stream';
import { createWriteStream, WriteStream } from 'fs';
import fetch, { Response } from 'node-fetch';
import mocked = jest.mocked;
jest.mock('node-fetch');
jest.mock('fs');
describe('downloadfile', () => {
const mockedFetch = fetch as jest.MockedFunction<typeof fetch>;
const response = Promise.resolve({
ok: true,
status: 200,
body: {
pipe: jest.fn(),
on: jest.fn(),
},
});
const mockWriteable = new PassThrough();
mocked(createWriteStream).mockReturnValueOnce(mockWriteable as unknown as WriteStream);
mockedFetch.mockImplementation(() => response as unknown as Promise<Response>);
it('should work', async () => {
await downloadfile();
}),
);
});
抛出:
Cannot read property 'length' of undefined
TypeError: Cannot read property 'length' of undefined
at GlobSync.Object.<anonymous>.GlobSync._readdirEntries (//node_modules/glob/sync.js:300:33)
at GlobSync.Object.<anonymous>.GlobSync._readdir (//node_modules/glob/sync.js:288:17)
at GlobSync.Object.<anonymous>.GlobSync._processReaddir (//node_modules/glob/sync.js:137:22)
at GlobSync.Object.<anonymous>.GlobSync._process (/api/node_modules/glob/sync.js:132:10)
解决方案是什么?
我发现错误与测试无关。它更多地与您的测试运行器有关。 Glob 是一个用于在项目中搜索文件的库。检查您的测试运行命令。尝试转义引号 \ 双引号。
您的包 JSON 将提供比您的测试文件更多的信息。
有些人报告说可以通过重新安装节点模块来解决此问题 尝试
- 删除 node_modules 文件夹
- 删除文件 package-lock.json
- 运行 npm install 以重新安装包依赖项。
您的基本函数有一个获取函数,该函数需要一个 URL,响应主体(数据)通过该 URL 传输到写入流,然后写入文件系统。
模拟获取有点繁琐,因为 jest mock 对 ES 模块的支持有限,更多信息 这里 和 这里 。因此,最好通过让 fetch 从 URL 下载文件来测试它,然后测试下载文件的完整性。
现在,在您的测试用例中调用该函数,如下所示:
const fileURL = "https://interactive-examples.mdn.mozilla.net/media/examples/lizard.png";
const fileDownloadPath = "/home/<your-username>/Downloads/downloaded_lizard.png";
// ----> Executing the function to be tested.
await downloadFile(fileURL, fileDownloadPath);
接下来,下载文件后,我们可以验证文件是否存在于下载路径中,然后测试其完整性。
完整性检查涉及下载的文件校验和(在我们的例子中是 md5)应与从服务器检索到的校验和相匹配。
如果您使用的是 AWS S3 文件 URL,那么它很可能应该在名为“ETag”的标头中返回校验和,还请注意,这并非在每种情况下都适用,但至少对于小文件而言,这是正确的。
如果 URL 中没有校验和,您可以自己使用指定的 URL 下载文件,并在本地计算机上计算其 md5 校验和,然后将此校验和分配给测试用例内的 const 变量,然后可以进一步使用该变量来测试文件。
const access_res = await fsp.access(fileDownloadPath, fs.constants.F_OK); // File should be accessible
expect(access_res).toEqual(undefined); // denotes success
// Compare md5 checksum of downloaded file with server file.
const file_buffer = fs.readFileSync(fileDownloadPath);
const downloaded_file_md5_checksum = createHash('md5').update(file_buffer).digest('hex');
const response_obj = await fetch(fileURL, { method: 'HEAD' });
const file_etag = JSON.parse(response_obj.headers.get('ETag'));
expect(downloaded_file_md5_checksum).toEqual(file_etag); // Should match
这是使用 ES 模块的完整工作示例:
import { jest } from '@jest/globals';
import fs from 'node:fs';
import fsp from 'node:fs/promises';
const { createHash } = await import('node:crypto');
import fetch from 'node-fetch';
import { downloadFile } from '../index';
// jest.setTimeout(15000); // in ms. Override if downloading big files.
describe("Download service", () => {
it("should download the file from provided url", async () => {
const fileURL = "https://interactive-examples.mdn.mozilla.net/media/examples/lizard.png";
const fileDownloadPath = "/home/<your-username>/Downloads/downloaded_lizard.png";
// ----> Executing the function to be tested.
await downloadFile(fileURL, fileDownloadPath);
// ----> Testing if the file exists on fs.
const access_res = await fsp.access(fileDownloadPath, fs.constants.F_OK);
expect(access_res).toEqual(undefined); // denotes success
// ----> Comparing downloaded file checksum with server file checksum.
const file_buffer = fs.readFileSync(fileDownloadPath);
const downloaded_file_md5_checksum = createHash('md5').update(file_buffer).digest('hex');
const response_obj = await fetch(fileURL, { method: 'HEAD' }); // Fetching checksum from server.
const file_etag = JSON.parse(response_obj.headers.get('ETag'));
expect(downloaded_file_md5_checksum).toEqual(file_etag); // Should match
// ----> Finally remove downloaded file from local system.
const rm_res = await fsp.unlink(fileDownloadPath);
expect(rm_res).toEqual(undefined); // denotes success
})
})