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

如何在Jest中修改单例类内部函数的Mock返回值?

问题描述

我在应用类中引入了模块内的单例类MySingleton,想要修改其内部函数isDecision的Mock返回值。尝试过两种方式但均未成功,不清楚修改单例内部函数Mock实现的正确流程。

尝试过的两种方式:

  • 监听单例:
jest.spyOn(MySingleton, "getInstance").mockImplementation({
  isDecision: jest.fn(() => false),
});
  • 内联调用Mock:
MySingleton.getInstance().isDecision.mockReturnValue(false);

单例类结构

export default class MySingleton {
    
  private static _instance: MySingleton;

  static getInstance(): MySingleton {
    if (!MySingleton._instance) {
      MySingleton._instance = new MySingleton();
    }

    return MySingleton._instance;
  }

  isDecision(): boolean {
    return true;
  }
}

消费类代码

import { MySingleton } from "@my-singleton";

export class Consumer {
  
  private _singleton: MySingleton;

  constructor() {
    this._singleton = MySingleton.getInstance();
  }

  utilityFunction(): boolean {
    return this._singleton.isDecision();
  }
}

测试代码

jest.mock("@my-singleton", () => ({
  __esModule: true,
  MySingleton: {
    getInstance: jest.fn().mockImplementation(() => ({
      isDecision: jest.fn(() => true),
    })),
  },
}));

describe("Consumer", () => {
  let consumer;

  beforeEach(() => {
    consumer = new Consumer();
  });
  
  describe("test decision is true case", () => {
    
    test("should return true", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(true);
    });
  });

  describe("test decision is false case", () => {
    
    beforeEach(() => {
      // 如何修改单例的IsDecsion使其返回false?
    });

    test("should return false", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(false);
    });
  });
});

请问如何在测试的beforeEach中修改isDecision的Mock返回值为false?


解决方案

核心问题在于当前jest.mock的实现导致getInstance的返回对象无法被稳定修改,或者修改时机不对。以下是两种可行的解决方法:

方法一:固定Mock单例实例

提前创建一个包含isDecision Mock函数的对象,让getInstance始终返回该对象,这样修改isDecision的返回值会直接作用到所有使用该实例的地方:

// 提前定义全局Mock单例对象
const mockSingletonInstance = {
  isDecision: jest.fn(() => true),
};

// 修改jest.mock的实现,让getInstance返回固定的Mock对象
jest.mock("@my-singleton", () => ({
  __esModule: true,
  MySingleton: {
    getInstance: jest.fn().mockReturnValue(mockSingletonInstance),
  },
}));

describe("Consumer", () => {
  let consumer;

  beforeEach(() => {
    // 每次测试前重置isDecision的默认返回值为true,避免测试污染
    mockSingletonInstance.isDecision.mockReturnValue(true);
    consumer = new Consumer();
  });
  
  describe("test decision is true case", () => {
    test("should return true", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(true);
    });
  });

  describe("test decision is false case", () => {
    beforeEach(() => {
      // 修改isDecision的返回值为false
      mockSingletonInstance.isDecision.mockReturnValue(false);
    });

    test("should return false", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(false);
    });
  });
});

这种方式的优势是实例唯一,修改一次就能全局生效,同时外层beforeEach会在每次测试前重置默认值,避免不同测试用例之间的干扰。

方法二:调整Mock修改时机

如果不想固定实例,需要确保修改isDecision的操作在创建Consumer实例之前执行,这样Consumer构造函数调用getInstance时,拿到的就是已经修改过返回值的Mock对象:

jest.mock("@my-singleton", () => ({
  __esModule: true,
  MySingleton: {
    getInstance: jest.fn().mockImplementation(() => ({
      isDecision: jest.fn(() => true),
    })),
  },
}));

describe("Consumer", () => {
  let consumer;

  // 外层beforeEach只重置getInstance的Mock实现,不创建Consumer
  beforeEach(() => {
    MySingleton.getInstance.mockImplementation(() => ({
      isDecision: jest.fn(() => true),
    }));
  });
  
  describe("test decision is true case", () => {
    beforeEach(() => {
      consumer = new Consumer();
    });

    test("should return true", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(true);
    });
  });

  describe("test decision is false case", () => {
    beforeEach(() => {
      // 先获取Mock实例,修改isDecision返回值,再创建Consumer
      const mockInstance = MySingleton.getInstance();
      mockInstance.isDecision.mockReturnValue(false);
      consumer = new Consumer();
    });

    test("should return false", () => {
      const decision = consumer.utilityFunction(); 
      expect(decision).toBe(false);
    });
  });
});

原方法失败原因

  1. 第一种方式错误:mockImplementation需要传入函数,你直接传了对象,正确写法应为mockImplementation(() => ({ isDecision: jest.fn(() => false) })),但即便修正,若Consumer实例已创建,它引用的还是之前的实例,修改不会生效。
  2. 第二种方式时机错误:如果Consumer实例在修改isDecision之前就已创建,它引用的是旧的isDecision函数,新修改的是后续调用getInstance返回的对象,无法影响已存在的Consumer实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 08:12:08