You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何模拟基于node-fetch实现的文件下载函数?单元测试出现‘Cannot read property 'length' of undefined’错误的解决方法

解决Jest测试node-fetch文件下载函数时的Glob错误

你遇到的这个Cannot read property 'length' of undefined错误,本质是因为你用jest.mock('fs')直接Mock了整个fs模块——而Jest内部依赖的glob库需要调用原生的fs方法(比如readdir)来查找测试文件,被完全Mock后这些方法返回undefined,导致glob处理目录条目时崩溃。

下面是具体的修复方案和优化后的测试代码:

1. 只Mock需要的FS方法,保留原生实现

不要全盘Mock fs模块,而是只Mock你的downloadFile函数用到的createWriteStream方法,让其他fs方法保持原生行为:

// 替换原来的jest.mock('fs')
jest.mock('fs', () => ({
  // 导入fs的原生实现
  ...jest.requireActual('fs'),
  // 只Mock我们需要的createWriteStream
  createWriteStream: jest.fn()
}));

2. 修正测试代码的其他问题

你的测试里还有几个小问题需要调整:

  • 调用downloadFile时没有传入必要的url和path参数,函数会因为参数缺失报错
  • 之前Mock的response body不够真实,用PassThrough流模拟会更贴近实际场景

完整修正后的测试代码

import { PassThrough } from 'stream'; 
import { createWriteStream, WriteStream } from 'fs'; 
import fetch, { Response } from 'node-fetch'; 
import mocked = jest.mocked; 

// 仅Mock createWriteStream,保留fs其他原生方法
jest.mock('fs', () => ({
  ...jest.requireActual('fs'),
  createWriteStream: jest.fn()
}));
jest.mock('node-fetch'); 

describe('downloadFile', () => { 
  const mockedFetch = fetch as jest.MockedFunction<typeof fetch>; 

  it('should download and save the file successfully', async () => { 
    // 模拟response的body流(用PassThrough模拟可管道的流)
    const mockResponseBody = new PassThrough();
    // 创建符合node-fetch规范的Response对象
    const mockResponse = new Response(mockResponseBody, {
      status: 200,
      ok: true
    });
    // 让fetch返回这个模拟的response
    mockedFetch.mockResolvedValue(mockResponse);

    // 模拟createWriteStream返回的写入流
    const mockWriteStream = new PassThrough() as unknown as WriteStream;
    mocked(createWriteStream).mockReturnValue(mockWriteStream);

    // 调用downloadFile并传入必要参数
    await downloadFile('https://example.com/test-file.txt', './local-file.txt');

    // 验证关键行为是否正确执行
    expect(mockedFetch).toHaveBeenCalledWith('https://example.com/test-file.txt');
    expect(createWriteStream).toHaveBeenCalledWith('./local-file.txt');
    expect(mockResponseBody.pipe).toHaveBeenCalledWith(mockWriteStream);
  });

  // 可选:测试错误场景
  it('should reject when stream encounters an error', async () => {
    const mockResponseBody = new PassThrough();
    const mockResponse = new Response(mockResponseBody, { status: 200, ok: true });
    mockedFetch.mockResolvedValue(mockResponse);

    const mockWriteStream = new PassThrough() as unknown as WriteStream;
    mocked(createWriteStream).mockReturnValue(mockWriteStream);

    // 模拟流错误
    const testError = new Error('Stream write failed');
    setTimeout(() => mockResponseBody.emit('error', testError), 0);

    // 验证函数是否正确reject错误
    await expect(downloadFile('https://example.com/error.txt', './error-file.txt')).rejects.toThrow(testError);
  });
});

额外注意事项

  • 确保你的downloadFile函数正确处理了流的错误事件(就像你原代码里那样监听res.body.on("error", reject)),否则测试错误场景时可能不会按预期reject。
  • 如果后续需要Mock其他fs方法,都可以用...jest.requireActual('fs')保留原生实现,只覆盖需要的方法,避免类似的第三方库依赖问题。

内容的提问来源于stack exchange,提问作者user3761308

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 21:02:27