开发者问题收集

模拟测试使用 node-fetch 下载文件的功能?

2022-05-19
2082

此答案提供了以下使用 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)

解决方案是什么?

2个回答

我发现错误与测试无关。它更多地与您的测试运行器有关。 Glob 是一个用于在项目中搜索文件的库。检查您的测试运行命令。尝试转义引号 \ 双引号。

您的包 JSON 将提供比您的测试文件更多的信息。

有些人报告说可以通过重新安装节点模块来解决此问题 尝试

  1. 删除 node_modules 文件夹
  2. 删除文件 package-lock.json
  3. 运行 npm install 以重新安装包依赖项。
zamuka
2022-05-27

您的基本函数有一个获取函数,该函数需要一个 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
  })
})
Dinkar Jain
2022-06-06