更新network库及core库优化
This commit is contained in:
384
packages/network-client/tests/NetworkClient.integration.test.ts
Normal file
384
packages/network-client/tests/NetworkClient.integration.test.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* NetworkClient 集成测试
|
||||
* 测试网络客户端的完整功能,包括依赖注入和错误处理
|
||||
*/
|
||||
|
||||
import { NetworkClient } from '../src/core/NetworkClient';
|
||||
|
||||
// Mock 所有外部依赖
|
||||
jest.mock('@esengine/ecs-framework', () => ({
|
||||
Core: {
|
||||
scene: null,
|
||||
schedule: {
|
||||
scheduleRepeating: jest.fn((callback: Function, interval: number) => ({
|
||||
stop: jest.fn()
|
||||
}))
|
||||
}
|
||||
},
|
||||
Emitter: jest.fn().mockImplementation(() => ({
|
||||
emit: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
removeAllListeners: jest.fn()
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.mock('@esengine/ecs-framework-network-shared', () => ({
|
||||
NetworkValue: {},
|
||||
generateMessageId: jest.fn(() => 'test-message-id-123'),
|
||||
generateNetworkId: jest.fn(() => 12345),
|
||||
NetworkUtils: {
|
||||
generateMessageId: jest.fn(() => 'test-message-id-456'),
|
||||
calculateDistance: jest.fn(() => 100),
|
||||
isNodeEnvironment: jest.fn(() => false),
|
||||
isBrowserEnvironment: jest.fn(() => true)
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock WebSocket
|
||||
class MockWebSocket {
|
||||
public readyState: number = WebSocket.CONNECTING;
|
||||
public onopen: ((event: Event) => void) | null = null;
|
||||
public onclose: ((event: CloseEvent) => void) | null = null;
|
||||
public onmessage: ((event: MessageEvent) => void) | null = null;
|
||||
public onerror: ((event: Event) => void) | null = null;
|
||||
|
||||
constructor(public url: string, public protocols?: string | string[]) {}
|
||||
|
||||
send(data: string | ArrayBuffer | Blob): void {}
|
||||
close(code?: number, reason?: string): void {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
if (this.onclose) {
|
||||
this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(global as any).WebSocket = MockWebSocket;
|
||||
(global as any).WebSocket.CONNECTING = 0;
|
||||
(global as any).WebSocket.OPEN = 1;
|
||||
(global as any).WebSocket.CLOSING = 2;
|
||||
(global as any).WebSocket.CLOSED = 3;
|
||||
|
||||
describe('NetworkClient 集成测试', () => {
|
||||
let client: NetworkClient;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (client) {
|
||||
client.disconnect().catch(() => {});
|
||||
client = null as any;
|
||||
}
|
||||
});
|
||||
|
||||
describe('依赖注入测试', () => {
|
||||
it('应该正确处理所有依赖模块', () => {
|
||||
expect(() => {
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
}).not.toThrow();
|
||||
|
||||
expect(client).toBeInstanceOf(NetworkClient);
|
||||
});
|
||||
|
||||
it('应该正确使用network-shared中的工具函数', () => {
|
||||
const { generateMessageId, NetworkUtils } = require('@esengine/ecs-framework-network-shared');
|
||||
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
|
||||
// 验证network-shared模块被正确导入
|
||||
expect(generateMessageId).toBeDefined();
|
||||
expect(NetworkUtils).toBeDefined();
|
||||
});
|
||||
|
||||
it('应该正确使用ecs-framework中的Core模块', () => {
|
||||
const { Core } = require('@esengine/ecs-framework');
|
||||
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
|
||||
expect(Core).toBeDefined();
|
||||
expect(Core.schedule).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('构造函数错误处理', () => {
|
||||
it('应该处理network-shared模块导入失败', () => {
|
||||
// 重置模块并模拟导入失败
|
||||
jest.resetModules();
|
||||
jest.doMock('@esengine/ecs-framework-network-shared', () => {
|
||||
throw new Error('network-shared模块导入失败');
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
const { NetworkClient } = require('../src/core/NetworkClient');
|
||||
new NetworkClient({
|
||||
transportType: 'websocket',
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('应该处理ecs-framework模块导入失败', () => {
|
||||
// 重置模块并模拟导入失败
|
||||
jest.resetModules();
|
||||
jest.doMock('@esengine/ecs-framework', () => {
|
||||
throw new Error('ecs-framework模块导入失败');
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
const { NetworkClient } = require('../src/core/NetworkClient');
|
||||
new NetworkClient({
|
||||
transportType: 'websocket',
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('应该处理传输层构造失败', () => {
|
||||
// Mock传输层构造函数抛出异常
|
||||
const originalWebSocket = (global as any).WebSocket;
|
||||
(global as any).WebSocket = jest.fn(() => {
|
||||
throw new Error('WebSocket不可用');
|
||||
});
|
||||
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
|
||||
expect(client.connect()).rejects.toThrow();
|
||||
|
||||
// 恢复原始WebSocket
|
||||
(global as any).WebSocket = originalWebSocket;
|
||||
});
|
||||
});
|
||||
|
||||
describe('功能测试', () => {
|
||||
beforeEach(() => {
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('应该能够成功连接', async () => {
|
||||
const connectPromise = client.connect();
|
||||
|
||||
// 模拟连接成功
|
||||
setTimeout(() => {
|
||||
const transport = (client as any).transport;
|
||||
if (transport && transport.websocket && transport.websocket.onopen) {
|
||||
transport.websocket.readyState = WebSocket.OPEN;
|
||||
transport.websocket.onopen(new Event('open'));
|
||||
}
|
||||
}, 0);
|
||||
|
||||
await expect(connectPromise).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该能够发送消息', async () => {
|
||||
// 先连接
|
||||
const connectPromise = client.connect();
|
||||
setTimeout(() => {
|
||||
const transport = (client as any).transport;
|
||||
if (transport && transport.websocket && transport.websocket.onopen) {
|
||||
transport.websocket.readyState = WebSocket.OPEN;
|
||||
transport.websocket.onopen(new Event('open'));
|
||||
}
|
||||
}, 0);
|
||||
await connectPromise;
|
||||
|
||||
// 发送消息
|
||||
const message = {
|
||||
type: 'custom' as const,
|
||||
data: { test: 'message' },
|
||||
reliable: true
|
||||
};
|
||||
|
||||
// NetworkClient没有直接的sendMessage方法,它通过RPC调用
|
||||
});
|
||||
|
||||
it('应该能够正确断开连接', async () => {
|
||||
await expect(client.disconnect()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该返回正确的认证状态', () => {
|
||||
expect(client.isAuthenticated()).toBe(false);
|
||||
});
|
||||
|
||||
it('应该能够获取网络对象列表', () => {
|
||||
const networkObjects = client.getAllNetworkObjects();
|
||||
expect(Array.isArray(networkObjects)).toBe(true);
|
||||
expect(networkObjects.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('消息ID生成测试', () => {
|
||||
beforeEach(() => {
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('应该能够生成唯一的消息ID', () => {
|
||||
const messageId1 = (client as any).generateMessageId();
|
||||
const messageId2 = (client as any).generateMessageId();
|
||||
|
||||
expect(typeof messageId1).toBe('string');
|
||||
expect(typeof messageId2).toBe('string');
|
||||
expect(messageId1).not.toBe(messageId2);
|
||||
});
|
||||
|
||||
it('生成的消息ID应该符合预期格式', () => {
|
||||
const messageId = (client as any).generateMessageId();
|
||||
|
||||
// 检查消息ID格式(时间戳 + 随机字符串)
|
||||
expect(messageId).toMatch(/^[a-z0-9]+$/);
|
||||
expect(messageId.length).toBeGreaterThan(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误恢复测试', () => {
|
||||
beforeEach(() => {
|
||||
client = new NetworkClient({
|
||||
transportType: 'websocket',
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
maxReconnectAttempts: 2,
|
||||
reconnectInterval: 100
|
||||
});
|
||||
});
|
||||
|
||||
it('连接失败后应该尝试重连', async () => {
|
||||
let connectAttempts = 0;
|
||||
const originalWebSocket = (global as any).WebSocket;
|
||||
|
||||
(global as any).WebSocket = jest.fn().mockImplementation(() => {
|
||||
connectAttempts++;
|
||||
const ws = new originalWebSocket('ws://localhost:8080');
|
||||
// 模拟连接失败
|
||||
setTimeout(() => {
|
||||
if (ws.onerror) {
|
||||
ws.onerror(new Event('error'));
|
||||
}
|
||||
}, 0);
|
||||
return ws;
|
||||
});
|
||||
|
||||
await expect(client.connect()).rejects.toThrow();
|
||||
|
||||
// 等待重连尝试
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
expect(connectAttempts).toBeGreaterThan(1);
|
||||
|
||||
// 恢复原始WebSocket
|
||||
(global as any).WebSocket = originalWebSocket;
|
||||
});
|
||||
|
||||
it('达到最大重连次数后应该停止重连', async () => {
|
||||
const maxAttempts = 2;
|
||||
client = new NetworkClient({
|
||||
transportType: 'websocket',
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
maxReconnectAttempts: maxAttempts,
|
||||
reconnectInterval: 50
|
||||
});
|
||||
|
||||
let connectAttempts = 0;
|
||||
const originalWebSocket = (global as any).WebSocket;
|
||||
|
||||
(global as any).WebSocket = jest.fn().mockImplementation(() => {
|
||||
connectAttempts++;
|
||||
const ws = new originalWebSocket('ws://localhost:8080');
|
||||
setTimeout(() => {
|
||||
if (ws.onerror) {
|
||||
ws.onerror(new Event('error'));
|
||||
}
|
||||
}, 0);
|
||||
return ws;
|
||||
});
|
||||
|
||||
await expect(client.connect()).rejects.toThrow();
|
||||
|
||||
// 等待所有重连尝试完成
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
expect(connectAttempts).toBeLessThanOrEqual(maxAttempts + 1);
|
||||
|
||||
// 恢复原始WebSocket
|
||||
(global as any).WebSocket = originalWebSocket;
|
||||
});
|
||||
});
|
||||
|
||||
describe('内存泄漏防护测试', () => {
|
||||
it('断开连接时应该清理所有资源', async () => {
|
||||
client = new NetworkClient({
|
||||
transport: 'websocket',
|
||||
transportConfig: {
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
|
||||
const { Emitter } = require('@esengine/ecs-framework');
|
||||
const emitterInstance = Emitter.mock.results[Emitter.mock.results.length - 1].value;
|
||||
|
||||
await client.disconnect();
|
||||
|
||||
expect(emitterInstance.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('多次创建和销毁客户端不应该造成内存泄漏', () => {
|
||||
const initialEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length;
|
||||
|
||||
// 创建和销毁多个客户端实例
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const tempClient = new NetworkClient({
|
||||
transportType: 'websocket',
|
||||
host: 'localhost',
|
||||
port: 8080
|
||||
});
|
||||
tempClient.disconnect().catch(() => {});
|
||||
}
|
||||
|
||||
const finalEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length;
|
||||
|
||||
// 验证Emitter实例数量符合预期
|
||||
expect(finalEmitterCallCount - initialEmitterCallCount).toBe(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user