You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用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

火山引擎 最新活动