应优先mock单例对象本身而非其状态,通过工厂函数封装、jest.mock拦截、object.defineproperty覆盖不可配置全局属性,并在beforeeach中显式重置状态以保障测试隔离性。

直接用 jest.mock() 或 sinon.replace() 替换全局单例对象本身,比试图“模拟它的状态”更可靠。关键不是 mock 状态,而是控制单例的创建时机和返回值。
用工厂函数封装单例,便于测试时替换
避免在模块顶层直接 new 实例。把单例逻辑抽成可注入的工厂:
- 原写法(难测):
const apiClient = new ApiClient(); - 改写为:
export const createApiClient = () => new ApiClient(); - 业务代码中通过
createApiClient()获取实例,测试时用 jest.mock 拦截该模块,返回自定义 mock 实例
用 jest.mock + requireActual 实现“部分 mock”
保留原单例类结构,只替换关键方法或属性:
jest.mock('./apiClient', () => {<br> const actual = jest.requireActual('./apiClient');<br> return {<br> createApiClient: jest.fn(() => ({<br> fetchUser: jest.fn().mockResolvedValue({ id: 1, name: 'test' }),<br> token: 'mock-token'<br> }))<br> };<br>});- 这样既复用原模块路径,又精准控制返回对象的状态和行为
用 Object.defineProperty 模拟不可配置的全局属性
当单例挂载在 window 或 globalThis 上且属性为 configurable: false 时:
- 先备份:
const original = globalThis.MySingleton; - 用
Object.defineProperty覆盖(需设writable: true):Object.defineProperty(globalThis, 'MySingleton', { value: mockInstance, writable: true, configurable: true }); - 测试后恢复:
Object.defineProperty(globalThis, 'MySingleton', { value: original, writable: false, configurable: false });
状态一致性靠 setup/teardown 显式管理
单例状态容易跨测试污染,必须显式重置:
- Jest 中用
beforeEach(() => { mockInstance.reset?.() }) - 或在 mock 实例里内置
reset()方法,清空缓存、重置计数器、还原 token 等 - 避免依赖
jest.clearAllMocks(),它不处理自定义属性或闭包状态
不复杂但容易忽略:mock 单例的本质是接管它的生命周期,而不是修补它的状态快照。











