如何使用Jest编写远程文件流式下载API的测试用例
如何用Jest测试流式下载API?
问题背景
我有一个Express API /api/v1/download/:id,负责从远程服务器获取zip文件并以流的形式转发给用户,远程凭证和URL对用户完全隐藏。目前API功能正常,但作为Jest新手,我不知道怎么针对这个流式场景编写测试用例。我已经搭好了基础的测试框架,还用Supertest来发起请求,但不知道具体怎么验证流的传输是否正确。
我的API实现代码:
const express = require("express"); const axios = require("axios"); const { InternalServerError } = require("common-lib"); const router = express.Router(); router.get("/api/v1/download/:id", async (req, res, next) => { const { id } = req.params; try { const response = await axios.get( `${process.env.DOWNLOAD_URL}/artifacts/client_app/${id}`, { auth: { username: process.env.USERNAME, password: process.env.PASSWORD }, responseType: "stream" }); response.data.pipe(res); } catch (error) { return next(new InternalServerError(error.message)); } }); module.exports = { downloadByIdRouter: router }
测试基础框架:
const request = require("supertest"); const { app } = require("../../app"); describe("download by id test", () => { test.todo("starts the app download, if requested properly") });
解决方案:分步实现流式测试
1. 模拟axios依赖,避免真实调用远程服务
首先我们要用Jest的mock功能替换掉axios,这样测试时不会真的去请求远程服务器,完全在本地模拟场景。在测试文件顶部添加:
const axios = require("axios"); jest.mock("axios"); // 自动模拟axios的所有方法
2. 创建模拟的可读流作为响应数据
我们需要生成一个模拟的可读流,用来模拟远程服务器返回的zip文件内容。Node.js内置的stream模块可以帮我们快速创建这个流:
const { Readable } = require("stream"); // 辅助函数:创建模拟的可读流 function createMockStream(content) { const stream = new Readable(); stream.push(content); stream.push(null); // 标记流结束 return stream; }
3. 编写完整测试用例(成功+错误场景)
现在可以编写具体的测试逻辑,验证API在正常和异常场景下的表现:
describe("download by id test", () => { // 提前设置模拟环境变量,避免undefined错误 beforeAll(() => { process.env.DOWNLOAD_URL = "http://mock-remote-server"; process.env.USERNAME = "mock-user"; process.env.PASSWORD = "mock-pass"; }); test("downloads the app correctly when requested properly", async () => { // 1. 准备测试数据:用简单字符串模拟zip文件内容(实际项目可用二进制数据) const mockZipContent = "mock zip file content"; const mockStream = createMockStream(mockZipContent); // 2. 让axios.get返回模拟的响应 axios.get.mockResolvedValue({ data: mockStream }); // 3. 用Supertest发起请求,自动收集流式响应 const response = await request(app).get("/api/v1/download/client_app.zip"); // 4. 验证结果 expect(response.statusCode).toBe(200); expect(response.text).toBe(mockZipContent); // 验证流的内容是否正确转发 expect(axios.get).toHaveBeenCalledTimes(1); // 验证axios是否用正确的参数调用(包括认证信息和响应类型) expect(axios.get).toHaveBeenCalledWith( "http://mock-remote-server/artifacts/client_app/client_app.zip", expect.objectContaining({ auth: { username: "mock-user", password: "mock-pass" }, responseType: "stream" }) ); }); test("returns 500 error when remote download fails", async () => { // 模拟axios请求失败的场景 const mockError = new Error("Remote server unavailable"); axios.get.mockRejectedValue(mockError); const response = await request(app).get("/api/v1/download/client_app.zip"); // 验证错误处理是否正确 expect(response.statusCode).toBe(500); expect(response.text).toContain(mockError.message); expect(axios.get).toHaveBeenCalledTimes(1); }); });
关键细节说明
- 流的自动收集:Supertest会自动处理流式响应,我们可以通过
response.text(文本内容)或response.buffer()(二进制内容)获取完整的流数据进行验证。 - 环境变量模拟:在
beforeAll中设置模拟的环境变量,避免测试时出现undefined的情况。 - 错误场景覆盖:不要只测试成功路径,一定要验证API在远程服务故障时是否能正确抛出错误并返回500状态码。
内容的提问来源于stack exchange,提问作者Roy




