/** * 聊天 WebSocket 网关单元测试 * * 功能描述: * - 测试 ChatWebSocketGateway 的 WebSocket 连接管理 * - 验证消息路由和处理逻辑 * - 测试房间管理和广播功能 * * 测试范围: * - onModuleInit() - 模块初始化 * - onModuleDestroy() - 模块销毁 * - getConnectionCount() - 获取连接数 * - getAuthenticatedConnectionCount() - 获取认证连接数 * - getMapPlayerCounts() - 获取地图玩家数 * - getMapPlayers() - 获取地图玩家列表 * - sendToPlayer() - 单播消息 * - broadcastToMap() - 地图广播 * * @author moyin * @version 1.0.0 * @since 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; import { ChatWebSocketGateway } from './chat.gateway'; import { ChatService } from '../../business/chat/chat.service'; // Mock ws module jest.mock('ws', () => { const mockServerInstance = { on: jest.fn(), close: jest.fn(), }; const MockServer = jest.fn(() => mockServerInstance); return { Server: MockServer, OPEN: 1, __mockServerInstance: mockServerInstance, }; }); describe('ChatWebSocketGateway', () => { let gateway: ChatWebSocketGateway; let mockChatService: jest.Mocked>; beforeEach(async () => { // Reset mocks jest.clearAllMocks(); mockChatService = { setWebSocketGateway: jest.fn(), handlePlayerLogin: jest.fn(), handlePlayerLogout: jest.fn(), sendChatMessage: jest.fn(), updatePlayerPosition: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ ChatWebSocketGateway, { provide: ChatService, useValue: mockChatService }, ], }).compile(); gateway = module.get(ChatWebSocketGateway); }); afterEach(() => { jest.clearAllMocks(); }); describe('onModuleInit', () => { it('should initialize WebSocket server and set gateway reference', async () => { await gateway.onModuleInit(); expect(mockChatService.setWebSocketGateway).toHaveBeenCalledWith(gateway); }); it('should use default port 3001 when WEBSOCKET_PORT is not set', async () => { delete process.env.WEBSOCKET_PORT; await gateway.onModuleInit(); // Verify server was created (mock was called) const ws = require('ws'); expect(ws.Server).toHaveBeenCalledWith( expect.objectContaining({ port: 3001, path: '/game', }) ); }); it('should use custom port from environment variable', async () => { process.env.WEBSOCKET_PORT = '4000'; // Create new gateway instance to pick up env change const module: TestingModule = await Test.createTestingModule({ providers: [ ChatWebSocketGateway, { provide: ChatService, useValue: mockChatService }, ], }).compile(); const newGateway = module.get(ChatWebSocketGateway); await newGateway.onModuleInit(); const ws = require('ws'); expect(ws.Server).toHaveBeenCalledWith( expect.objectContaining({ port: 4000, path: '/game', }) ); delete process.env.WEBSOCKET_PORT; }); }); describe('onModuleDestroy', () => { it('should close WebSocket server when it exists', async () => { await gateway.onModuleInit(); await gateway.onModuleDestroy(); const ws = require('ws'); expect(ws.__mockServerInstance.close).toHaveBeenCalled(); }); it('should not throw when server does not exist', async () => { // Don't call onModuleInit, so server is undefined await expect(gateway.onModuleDestroy()).resolves.not.toThrow(); }); }); describe('getConnectionCount', () => { it('should return 0 when no clients connected', () => { expect(gateway.getConnectionCount()).toBe(0); }); }); describe('getAuthenticatedConnectionCount', () => { it('should return 0 when no authenticated clients', () => { expect(gateway.getAuthenticatedConnectionCount()).toBe(0); }); }); describe('getMapPlayerCounts', () => { it('should return empty object when no rooms exist', () => { expect(gateway.getMapPlayerCounts()).toEqual({}); }); }); describe('getMapPlayers', () => { it('should return empty array for non-existent room', () => { expect(gateway.getMapPlayers('non_existent_map')).toEqual([]); }); }); describe('sendToPlayer', () => { it('should not throw when client does not exist', () => { expect(() => { gateway.sendToPlayer('non_existent_id', { type: 'test' }); }).not.toThrow(); }); }); describe('broadcastToMap', () => { it('should not throw when room does not exist', () => { expect(() => { gateway.broadcastToMap('non_existent_map', { type: 'test' }); }).not.toThrow(); }); it('should handle excludeId parameter', () => { expect(() => { gateway.broadcastToMap('non_existent_map', { type: 'test' }, 'exclude_id'); }).not.toThrow(); }); }); describe('IChatWebSocketGateway interface', () => { it('should implement all interface methods', () => { expect(typeof gateway.sendToPlayer).toBe('function'); expect(typeof gateway.broadcastToMap).toBe('function'); expect(typeof gateway.getConnectionCount).toBe('function'); expect(typeof gateway.getAuthenticatedConnectionCount).toBe('function'); expect(typeof gateway.getMapPlayerCounts).toBe('function'); expect(typeof gateway.getMapPlayers).toBe('function'); }); }); });