You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've written a socket.io client for our Flutter app. I would like to test it. I'm not very particular which kind of tests (integration / unit / etc) but I would prioritise clarity.
I've tried using the old implementation but it is too old and the dependencies do not resolve properly within my project.
How are you writing your tests for yourclient code?
PS: I used AI to create a minimal server that I could use for tests. It works surprisingly well. I post it here "as is" if anyone is interested (it only does what I need and nothing more).
import'dart:async';
import'dart:convert';
import'dart:io';
import'package:flutter/foundation.dart';
classSocketIOTestServer {
staticconstbool _debugEnabled =false;
SocketIOTestServer._();
staticFuture<SocketIOTestServer> start(String listeningPath) async {
final server =SocketIOTestServer._();
await server._start(listeningPath);
awaitFuture.delayed(constDuration(milliseconds:50));
return server;
}
Map<String, dynamic> authData = {};
HttpHeaders? connectionHeaders;
Stream<Map<String, dynamic>> get messageStream => _messageController.stream;
intget port => _server.port;
latefinalHttpServer _server;
WebSocket? _client;
finalStreamController<Map<String, dynamic>> _messageController =StreamController.broadcast();
Map<String, dynamic>? _pendingBinaryMessage;
finalList<List<int>> _pendingBinaryData = [];
Future<void> _start(String listeningPath) async {
_server =awaitHttpServer.bind('localhost', 0);
_serverLog('🚀 Dart Socket.IO test server started on port ${_server.port}');
_serverLog('📍 Socket path: $listeningPath');
_server.listen((HttpRequest request) async {
connectionHeaders = request.headers;
_serverLog('📥 Incoming request: ${request.method} ${request.uri}');
_serverLog('Headers: ${request.headers}');
_serverLog('Query params: ${request.uri.queryParameters}');
try {
final isUpgradeRequest =WebSocketTransformer.isUpgradeRequest(request);
_serverLog('WebSocket upgrade: $isUpgradeRequest');
if (request.uri.path == listeningPath && isUpgradeRequest) {
_serverLog('🔌 Handling WebSocket upgrade');
await_handleWebSocketUpgrade(request);
} else {
final response = request.response;
response.statusCode =HttpStatus.notFound;
response.headers.add('Access-Control-Allow-Origin', '*');
response.write('Not Found: ${request.uri.path}');
await response.close();
}
} catch (e) {
_serverLog('❌ Error handling request: $e');
final response = request.response;
response.statusCode =HttpStatus.internalServerError;
await response.close();
}
});
}
voidreset() {
authData = {};
connectionHeaders =null;
}
Future<void> _handleWebSocketUpgrade(HttpRequest request) async {
final webSocket =awaitWebSocketTransformer.upgrade(request);
_serverLog('🔌 WebSocket client connected!');
// Engine.IO v4 handshake for Socket.IO v3 compatibilityfinal sessionId ='test-${DateTime.now().millisecondsSinceEpoch}';
// Send Engine.IO OPEN packet (type 0) with proper v4 formatfinal openPacket = {
'sid': sessionId,
'upgrades': [],
'pingInterval':25000,
'pingTimeout':20000,
'maxPayload':1000000,
};
webSocket.add('0${jsonEncode(openPacket)}');
_serverLog('📤 Sent Engine.IO v4 OPEN: 0${jsonEncode(openPacket)}');
webSocket.listen(
(message) async {
_serverLog('📨 Received raw message: $message');
await_handleSocketIOMessage(webSocket, message);
},
onDone: () {
_client =null;
_serverLog(
'🔌 WebSocket client disconnected.',
);
},
onError: (error) {
_client =null;
_serverLog('❌ WebSocket error: $error');
},
);
_client = webSocket;
}
Future<void> _handleSocketIOMessage(
WebSocket webSocket,
dynamic rawMessage,
) async {
try {
// Handle binary data (comes as List<int>)if (rawMessage isList<int>) {
_serverLog('📦 Received binary data: ${rawMessage.length} bytes');
_pendingBinaryData.add(rawMessage);
// If we have a pending message waiting for binary data, complete itif (_pendingBinaryMessage !=null) {
_completeBinaryMessage();
}
return;
}
// Parse Socket.IO message formatfinal messageStr = rawMessage.toString();
_serverLog('📨 Processing message: $messageStr');
//CONNECT message to namespace (Socket.IO v3 packet type 4, namespace 0)if (messageStr.startsWith('40')) {
_serverLog('🔗 Client sending CONNECT to default namespace');
//Auth data if present (Socket.IO v3 supports auth in CONNECT packet)if (messageStr.length >2) {
try {
authData =jsonDecode(messageStr.substring(2)) asMap<String, dynamic>;
_serverLog('🔐 Auth data captured: $authData');
} catch (e) {
_serverLog(
'⚠️ Could not parse auth data: ${messageStr.substring(2)}',
);
}
}
// Socket.IO v3:// Respond with CONNECT packet containing sid and optional pid// The client expects: packet['data']['sid'] to be presentfinal connectResponse = {
'sid':'dart-session-${DateTime.now().millisecondsSinceEpoch}',
};
// Send CONNECT acknowledgment with proper data structurefinal encodedResponse =jsonEncode(connectResponse);
webSocket.add('40$encodedResponse');
_serverLog(
'✅ Sent Socket.IO v3 CONNECT with sid: 40$encodedResponse',
);
// Binary event message (451- means binary event with 1 attachment)
} elseif (messageStr.startsWith('451-')) {
_serverLog('📦 Binary event message detected');
final jsonData = messageStr.substring(4); // Remove '451-'finalList<dynamic> eventData =jsonDecode(jsonData);
final eventPayload = eventData.length >1? eventData[1] : {};
// Store the message, waiting for binary data
_pendingBinaryMessage = {
'event': eventData[0],
'payload': eventPayload,
'timestamp':DateTime.now().millisecondsSinceEpoch,
};
// If we already have binary data, complete the messageif (_pendingBinaryData.isNotEmpty) {
_completeBinaryMessage();
}
// Event message (type 4, namespace 2)
} elseif (messageStr.startsWith('42')) {
final jsonData = messageStr.substring(2);
finalList<dynamic> eventData =jsonDecode(jsonData);
final eventPayload = eventData.length >1? eventData[1] : {};
final messageData = {
'event': eventData[0],
'payload': eventPayload,
'timestamp':DateTime.now().millisecondsSinceEpoch,
};
_serverLog('📤 Message $messageData');
_messageController.add(messageData);
// DISCONNECT packet (Socket.IO packet type 4, namespace 1)
} elseif (messageStr =='41') {
_serverLog('🔌 Client sending DISCONNECT from namespace');
} elseif (messageStr =='2') {
// Engine.IO ping message - respond with pong
webSocket.add('3');
_serverLog('🏓 Ping-pong');
} else {
_serverLog('❓ Unknown message format: $messageStr');
}
} catch (e) {
_serverLog('❌ Error parsing message: $e');
}
}
voidemitEvent(String eventName, Map<String, dynamic> data) {
final eventMessage = [eventName, data];
final socketIOMessage ='42${jsonEncode(eventMessage)}';
if (_client !=null) {
_client!.add(socketIOMessage);
_serverLog('📤 Broadcasted event: $eventName');
} else {
throwException('No connected client');
}
}
Future<void> closeClientSocket() async {
returnawait _client?.close();
}
Future<void> stop() async {
_serverLog('🛑 Stopping Dart Socket.IO test server...');
awaitcloseClientSocket();
if (!_messageController.isClosed) {
await _messageController.close();
}
await _server.close();
_serverLog('✅ Dart Socket.IO test server stopped');
}
void_completeBinaryMessage() {
if (_pendingBinaryMessage !=null&& _pendingBinaryData.isNotEmpty) {
// Replace placeholders with actual binary datafinal payload = _pendingBinaryMessage!['payload'] asMap<String, dynamic>;
_replaceBinaryPlaceholders(payload, _pendingBinaryData);
_serverLog('📤 Complete binary message: $_pendingBinaryMessage');
_messageController.add(_pendingBinaryMessage!);
// Clear pending data
_pendingBinaryMessage =null;
_pendingBinaryData.clear();
}
}
void_replaceBinaryPlaceholders(
Map<String, dynamic> obj,
List<List<int>> binaryData,
) {
obj.forEach((key, value) {
if (value isMap<String, dynamic>) {
if (value['_placeholder'] ==true&& value['num'] isint) {
final index = value['num'] asint;
if (index < binaryData.length) {
obj[key] = binaryData[index]; // Replace with actual binary data
}
} else {
_replaceBinaryPlaceholders(value, binaryData);
}
} elseif (value isList) {
for (int i =0; i < value.length; i++) {
if (value[i] isMap<String, dynamic>) {
_replaceBinaryPlaceholders(value[i], binaryData);
}
}
}
});
}
void_serverLog(String log) {
if (_debugEnabled) {
debugPrint(log);
}
}
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
I've written a socket.io client for our Flutter app. I would like to test it. I'm not very particular which kind of tests (integration / unit / etc) but I would prioritise clarity.
I've tried using the old implementation but it is too old and the dependencies do not resolve properly within my project.
How are you writing your tests for yourclient code?
PS: I used AI to create a minimal server that I could use for tests. It works surprisingly well. I post it here "as is" if anyone is interested (it only does what I need and nothing more).
Beta Was this translation helpful? Give feedback.
All reactions