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

如何在jest+supertest集成测试中mock redis createClient的实现?

问题根本原因

你遇到的不是supertest的限制,是模块导入执行顺序导致的:

  • 单元测试中你先完成redis模块mock,再实例化RedisSecretRepository,此时调用createClient用的就是你后续修改的mock返回值
  • 集成测试中你先执行import server from "../../src/server",server模块在导入阶段就已经完成了Redis客户端的初始化(提前调用了createClient创建实例),你在测试用例内修改mockReturnValue的时间点晚于客户端实例的创建时间,自然不会生效。

解决方案

方案1:调整导入顺序,用动态导入加载server

把server的导入放到mock修改完成之后,通过动态导入的方式避免模块预执行:

import supertest from "supertest";
import { createClient } from "redis";

jest.mock("redis");
const mockedCreateClient = createClient as jest.MockedFunction<any>;

describe("Get Secrets By Id Integration Tests", () => {
    beforeEach(() => {
        // 每次用例执行前重置模块缓存,保证导入的server是全新实例
        jest.resetModules();
    });

    it("should retrieve a secret", async () => {
        // 先修改mock返回值
        const mockedRedisClient = {
            connect: jest.fn(),
            get: jest.fn(async () => "123qwe"),
            on: jest.fn(),
        };
        mockedCreateClient.mockReturnValue(mockedRedisClient);
        
        // 再动态导入server,此时server初始化Redis用的就是上面的mock
        const server = (await import("../../src/server")).default;
        const request = supertest(server.app);

        const res = await request.get("/api/v1/secrets/123456qwerty");
        expect(res.status).toBe(200);
        expect(res.body).toEqual({ secret: "123qwe" });
    });

    it("should return 404 when secret not exists", async () => {
        // 每个用例都可以自定义mock行为
        const mockedRedisClient = {
            connect: jest.fn(),
            get: jest.fn(async () => null),
            on: jest.fn(),
        };
        mockedCreateClient.mockReturnValue(mockedRedisClient);
        const server = (await import("../../src/server")).default;
        const request = supertest(server.app);

        const res = await request.get("/api/v1/secrets/not_exists_id");
        expect(res.status).toBe(404);
    });
});

方案2:将Redis客户端改为懒加载模式

修改业务代码的Redis客户端初始化逻辑,不要在模块导入阶段就创建实例,改成第一次被调用时才初始化,保证测试用例修改mock的时间足够早:

// 原代码(模块导入时就创建客户端)
const redisClient = createClient();
export class RedisSecretRepository {
  constructor() {
    this.client = redisClient;
  }
}

// 修改为懒加载
export class RedisSecretRepository {
  constructor() {
    this.client = null;
  }

  async getClient() {
    if (!this.client) {
      this.client = createClient();
      await this.client.connect();
    }
    return this.client;
  }

  async getSecretByUrlId(urlId) {
    const client = await this.getClient();
    return client.get(urlId.toString());
  }
}

方案3:使用共享mock客户端实例(无需修改业务代码)

在mock工厂函数中返回一个可修改的共享mock客户端实例,测试用例直接修改该实例的方法行为即可:

// 定义可修改的共享mock客户端
const mockRedisClient = {
  connect: jest.fn(),
  get: jest.fn(),
  on: jest.fn()
};
jest.mock("redis", () => ({
  createClient: jest.fn(() => mockRedisClient)
}));

import supertest from "supertest";
import server from "../../src/server";
const request = supertest(server.app);

describe("Get Secrets By Id Integration Tests", () => {
  beforeEach(() => {
    // 每个用例前重置所有mock的调用记录和返回值
    jest.clearAllMocks();
  });

  it("should retrieve a secret", async () => {
    // 直接修改共享实例的get方法返回值
    mockRedisClient.get.mockResolvedValue("123qwe");
    const res = await request.get("/api/v1/secrets/123456qwerty");
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ secret: "123qwe" });
  });

  it("should return 404 when secret not found", async () => {
    mockRedisClient.get.mockResolvedValue(null);
    const res = await request.get("/api/v1/secrets/invalid_id");
    expect(res.status).toBe(404);
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 11:36:03