javascript中无法直接mock构造函数,但可通过jest的jest.mock()、jest.fn()和jest.spyon()组合控制其返回对象及行为;推荐用jest.mock()替换类模块并自定义实现,次选spyon劫持全局类,或封装工厂函数再mock。

JavaScript 中无法直接用原生工具 Mock 构造函数(比如 new SomeClass()),但可以通过 Jest 提供的 jest.mock()、jest.fn() 和 jest.spyOn() 组合实现等效效果。关键不是“替换 new 行为本身”,而是控制构造函数返回的对象及其行为。
用 jest.mock() 替换整个类模块
这是最常用、最清晰的方式,适用于构造函数来自独立模块(如 ES6 class 文件)的场景:
- 在测试文件顶部调用
jest.mock('./SomeClass'),Jest 会自动用一个空 mock 类替代原始类; - 再通过
SomeClass.mockImplementation()自定义构造逻辑,例如返回带 stub 方法的对象; - 支持模拟实例方法、属性,甚至控制多次 new 的不同返回值。
示例:
// someClass.js
export class ApiClient {
constructor(url) {
this.url = url;
}
fetch() {
return fetch(this.url);
}
}
<p>// apiService.js
import { ApiClient } from './someClass';
export function createService() {
return new ApiClient('<a href="https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39">https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39</a>');
}</p><p>// apiService.test.js
import { createService } from './apiService';
import { ApiClient } from './someClass';</p><p>jest.mock('./someClass'); // 替换 ApiClient 模块</p><p>test('uses mocked ApiClient', () => {
const mockInstance = { fetch: jest.fn().mockResolvedValue({ data: 'ok' }) };
ApiClient.mockImplementation(() => mockInstance);</p><p>const service = createService();
expect(service.fetch).toBeCalledTimes(0);
service.fetch();
expect(service.fetch).toBeCalledTimes(1);
});
</p>
用 jest.spyOn() 拦截已有构造函数(不推荐用于模块级类)
当类已全局声明或在测试作用域内可访问时(如浏览器环境下的 window.MyClass),可用 spyOn + mockImplementation 劫持构造调用:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 必须确保被 spy 的对象是可写/可配置的(否则会报错);
- 需手动还原(
mockRestore()),避免影响其他测试; - 不适合 ESM 模块默认导出的类,因 import 绑定不可变。
示例:
class Logger {
constructor(level) {
this.level = level;
}
log(msg) { console.log(msg); }
}
<p>test('spy on global Logger', () => {
const spy = jest.spyOn(global, 'Logger').mockImplementation(function(level) {
this.level = level;
this.log = jest.fn();
});</p><p>const l = new Logger('debug');
expect(l.log).toBeDefined();
expect(l.log).not.toBeCalled();</p><p>spy.mockRestore(); // 必须还原
});
</p>
对依赖类做“代理式包装”(适合复杂初始化场景)
当构造函数内部有副作用(如读取 localStorage、发起请求),又不想改源码时,可在业务代码中引入一层工厂函数,再 mock 该函数:
- 把
new X()封装进createX()函数; - 测试中 mock 这个工厂函数,返回可控实例;
- 零侵入、无需 PowerMock 类工具,符合 Jest 设计哲学。
示例:
// service.js
import { Database } from './db';
<p>export function createDatabase() {
return new Database('prod.db');
}</p><p>export function loadData() {
const db = createDatabase();
return db.query('SELECT * FROM users');
}</p><p>// service.test.js
import { loadData } from './service';
import { createDatabase } from './service'; // 注意:需能导入该函数</p><p>jest.mock('./service', () => ({
...jest.requireActual('./service'),
createDatabase: jest.fn(),
}));</p><p>test('loads data with mocked DB', () => {
const mockDb = { query: jest.fn().mockResolvedValue([{ id: 1 }]) };
createDatabase.mockReturnValue(mockDb);</p><p>return expect(loadData()).resolves.toEqual([{ id: 1 }]);
});
</p>
不建议的做法:强行拦截 new(如 Proxy + new.target)
虽然技术上可用 Proxy 包裹函数并拦截 construct trap,但:
- Jest 不识别这种代理,mock 状态难管理;
- 破坏 instanceof 判断,影响类型校验和调试;
- 增加测试复杂度,得不偿失。
真正需要隔离的是“依赖行为”,不是 “new 这个语法”。优先重构为可注入、可替换的模式,比硬 mock 构造更可持续。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










