如何模拟基于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
相关产品推荐
相关产品推荐

