使用TypeScript+ws库时如何Mock WebSocket?(Jest环境)
针对ws库的Jest Mock最优方案
问题根源
你遇到的TypeError: ws_2.on is not a function,是因为mock-socket实现的WebSocket遵循浏览器标准(用addEventListener绑定事件),但Node.js的ws库使用自有API(on方法绑定事件),二者API不兼容导致的。
方案1:直接Mock ws库(无额外依赖,推荐)
不需要引入第三方mock库,直接用Jest原生mock功能模拟ws库核心API,完全匹配ws的调用方式。
步骤1:创建ws的Mock文件
在__tests__/mocks/ws.ts中编写如下代码:
export class WebSocket { private eventListeners: Record<string, Function[]> = {}; public readyState: number = 0; // CONNECTING constructor(public url: string) { // 模拟连接成功,触发open事件 process.nextTick(() => { this.readyState = 1; // OPEN this.emit('open'); }); } on(event: string, callback: Function) { if (!this.eventListeners[event]) { this.eventListeners[event] = []; } this.eventListeners[event].push(callback); } send(data: string) { // 模拟发送消息,可在此捕获内容用于断言 this.emit('message', { data }); } close() { this.readyState = 3; // CLOSED this.emit('close'); } private emit(event: string, ...args: any[]) { this.eventListeners[event]?.forEach(callback => callback(...args)); } } // 模拟ws库的其他导出(按需添加) export const WebSocketServer = jest.fn();
步骤2:配置Jest使用Mock
在jest.config.ts中添加mock路径映射:
export default { // 其他配置... moduleNameMapper: { '^ws$': '<rootDir>/__tests__/mocks/ws.ts' } };
步骤3:编写测试用例(MyClient.test.ts)
假设你的MyClient.ts是封装ws客户端的类,测试代码示例:
import { MyClient } from '../MyClient'; import { WebSocket } from 'ws'; // 类型断言,方便访问mock内部方法 const MockWebSocket = WebSocket as jest.MockedClass<typeof WebSocket>; describe('MyClient', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should connect and send message', async () => { const client = new MyClient('ws://localhost:8080'); // 断言连接初始化 expect(MockWebSocket).toHaveBeenCalledWith('ws://localhost:8080'); expect(client.isConnected).toBe(true); // 模拟服务器推送消息 const mockWsInstance = MockWebSocket.mock.instances[0]; mockWsInstance.emit('message', { data: 'server response' }); // 断言客户端处理消息 expect(client.lastReceivedMessage).toBe('server response'); // 测试发送消息 client.sendMessage('hello server'); expect(mockWsInstance.send).toHaveBeenCalledWith('hello server'); }); it('should handle connection close', () => { const client = new MyClient('ws://localhost:8080'); const mockWsInstance = MockWebSocket.mock.instances[0]; mockWsInstance.emit('close'); expect(client.isConnected).toBe(false); }); });
示例客户端代码(MyClient.ts)
import { WebSocket } from 'ws'; export class MyClient { private ws: WebSocket | null = null; public isConnected = false; public lastReceivedMessage: string | null = null; constructor(url: string) { this.ws = new WebSocket(url); this.ws.on('open', () => { this.isConnected = true; }); this.ws.on('message', (data) => { this.lastReceivedMessage = data.toString(); }); this.ws.on('close', () => { this.isConnected = false; this.ws = null; }); } sendMessage(message: string) { if (this.ws && this.isConnected) { this.ws.send(message); } } }
方案2:适配mock-socket的API
如果一定要用mock-socket,可以给它的WebSocket原型添加on方法,映射到addEventListener:
修改__tests__/mocks/ws.ts:
import { WebSocket as MockSocket } from 'mock-socket'; // 给mock-socket的WebSocket添加on方法,适配ws库API MockSocket.prototype.on = function(event: string, callback: Function) { this.addEventListener(event, callback); }; // 可选:添加once方法(如果客户端用到) MockSocket.prototype.once = function(event: string, callback: Function) { const wrapper = (...args: any[]) => { callback(...args); this.removeEventListener(event, wrapper); }; this.addEventListener(event, wrapper); }; export { MockSocket as WebSocket }; export const WebSocketServer = jest.fn();
配置Jest的moduleNameMapper和方案1一致,测试用例可结合mock-socket的Server类模拟服务器:
import { Server } from 'mock-socket'; import { MyClient } from '../MyClient'; describe('MyClient', () => { let mockServer: Server; beforeEach(() => { mockServer = new Server('ws://localhost:8080'); }); afterEach(() => { mockServer.stop(); }); it('should receive message from server', (done) => { const client = new MyClient('ws://localhost:8080'); mockServer.on('connection', (socket) => { socket.send('test message'); }); setTimeout(() => { expect(client.lastReceivedMessage).toBe('test message'); done(); }, 100); }); });
方案对比
- 方案1:无额外依赖,自定义mock逻辑精准匹配ws库API,适合需要精细控制mock行为的场景。
- 方案2:利用mock-socket现有功能,适配API后可快速模拟双向通信,适合完整WebSocket交互测试场景。
内容的提问来源于stack exchange,提问作者Kristi Jorgji
相关产品推荐
相关产品推荐

