From b698afc737c986cbe1f64a4e8cb926c44a8937ba Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 16 Feb 2025 07:36:09 +0100 Subject: [PATCH 1/2] feat: added basic (working) unit tests for WsprotocolBloc --- .../wsprotocol/bloc/wsprotocol_bloc.dart | 26 ++-- .../wsprotocol/bloc/wsprotocol_event.dart | 8 +- .../presentation/screens/wsprotocol_view.dart | 4 +- .../wsprotocol/bloc/wsprotocol_bloc_test.dart | 142 +++++++----------- 4 files changed, 84 insertions(+), 96 deletions(-) diff --git a/lib/features/wsprotocol/bloc/wsprotocol_bloc.dart b/lib/features/wsprotocol/bloc/wsprotocol_bloc.dart index 81ff50a..9b2de51 100644 --- a/lib/features/wsprotocol/bloc/wsprotocol_bloc.dart +++ b/lib/features/wsprotocol/bloc/wsprotocol_bloc.dart @@ -10,7 +10,7 @@ class WsprotocolBloc extends Bloc { TextEditingController wssTextController = TextEditingController(); TextEditingController randomStringController = TextEditingController(); - late WsprotocolRepoImpl repository; + WsprotocolRepoImpl repository = WsprotocolRepoImpl('wss://echo.websocket.events'); StreamSubscription? _messageSubscription; final List _messages = []; @@ -32,7 +32,7 @@ class WsprotocolBloc extends Bloc { Future _onConnectRequested(ConnectRequested event, Emitter emit) async { emit(WsprotocolConnecting()); try { - repository = WsprotocolRepoImpl(wssTextController.text); + repository = WsprotocolRepoImpl(event.socketUrl); repository.connect(); // Listen for messages from the repository. _messageSubscription = repository.messages.listen( @@ -55,10 +55,14 @@ class WsprotocolBloc extends Bloc { } Future _onDisconnectRequested(DisconnectRequested event, Emitter emit) async { - repository.disconnect(); - _messages.clear(); - await _messageSubscription?.cancel(); - emit(WsprotocolDisconnected()); + try { + repository.disconnect(); + _messages.clear(); + await _messageSubscription?.cancel(); + emit(WsprotocolDisconnected()); + } catch (e) { + emit(WsprotocolError(e.toString())); + } } Future _onSendMessageRequested(SendMessageRequested event, Emitter emit) async { @@ -83,9 +87,13 @@ class WsprotocolBloc extends Bloc { } Future _onMessageReceived(MessageReceived event, Emitter emit) async { - // Log the incoming message. - _messages.add("Received: ${event.message}"); - emit(WsprotocolConnected(messages: List.from(_messages))); + try { + // Log the incoming message. + _messages.add("Received: ${event.message}"); + emit(WsprotocolConnected(messages: List.from(_messages))); + } catch (e) { + emit(WsprotocolError(e.toString())); + } } Future _onConnectionErrorOccurred(ConnectionErrorOccurred event, Emitter emit) async { diff --git a/lib/features/wsprotocol/bloc/wsprotocol_event.dart b/lib/features/wsprotocol/bloc/wsprotocol_event.dart index ddcbaab..575a346 100644 --- a/lib/features/wsprotocol/bloc/wsprotocol_event.dart +++ b/lib/features/wsprotocol/bloc/wsprotocol_event.dart @@ -9,7 +9,13 @@ abstract class WsprotocolEvent extends Equatable { } /// Event to initiate a connection. -class ConnectRequested extends WsprotocolEvent {} +class ConnectRequested extends WsprotocolEvent { + final String socketUrl; + const ConnectRequested({required this.socketUrl}); + + @override + List get props => [socketUrl]; +} /// Event to close the connection. class DisconnectRequested extends WsprotocolEvent {} diff --git a/lib/features/wsprotocol/presentation/screens/wsprotocol_view.dart b/lib/features/wsprotocol/presentation/screens/wsprotocol_view.dart index 0c8766d..a3d106c 100644 --- a/lib/features/wsprotocol/presentation/screens/wsprotocol_view.dart +++ b/lib/features/wsprotocol/presentation/screens/wsprotocol_view.dart @@ -70,7 +70,9 @@ class _WsprotocolViewState extends State { protocolBloc.add(DisconnectRequested()); return; } - protocolBloc.add(ConnectRequested()); + protocolBloc.add(ConnectRequested( + socketUrl: protocolBloc.wssTextController.text + )); }, content: isLoading ? SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white)) diff --git a/test/wsprotocol/bloc/wsprotocol_bloc_test.dart b/test/wsprotocol/bloc/wsprotocol_bloc_test.dart index ac6f2e5..32d455a 100644 --- a/test/wsprotocol/bloc/wsprotocol_bloc_test.dart +++ b/test/wsprotocol/bloc/wsprotocol_bloc_test.dart @@ -1,93 +1,65 @@ -// import 'package:bloc_test/bloc_test.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:mocktail/mocktail.dart'; -// import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_bloc.dart'; -// import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_event.dart'; -// import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_state.dart'; -// import 'package:socket_probe/features/wsprotocol/data/repository/wsprotocol_repo_impl.dart'; -// import 'dart:async'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_bloc.dart'; +import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_event.dart'; +import 'package:socket_probe/features/wsprotocol/bloc/wsprotocol_state.dart'; -// class MockWsprotocolRepo extends Mock implements WsprotocolRepoImpl { -// @override -// void connect() { -// // Do nothing to avoid real WebSocket initialization -// } -// } +void main() { + group(WsprotocolBloc, () { + late WsprotocolBloc wsprotocolBloc; -// void main() { -// late WsprotocolBloc bloc; -// late MockWsprotocolRepo mockRepo; -// late StreamController messageController; + setUp(() { + wsprotocolBloc = WsprotocolBloc(); + }); -// setUp(() { -// mockRepo = MockWsprotocolRepo(); -// messageController = StreamController.broadcast(); + blocTest( + 'This test the websocket connection when ConnectedRequest is added', + build: () => wsprotocolBloc, + act: (bloc) => bloc.add(ConnectRequested(socketUrl: 'wss://echo.websocket.events')), + expect: () => [ + isA(), + isA(), + ], + ); -// // Stub repository methods -// when(() => mockRepo.messages).thenAnswer((_) => messageController.stream); -// when(() => mockRepo.connect()).thenReturn(null); -// when(() => mockRepo.disconnect()).thenReturn(null); + blocTest( + 'This test the websocket when DisconnectRequest is added', + build: () => wsprotocolBloc, + act: (bloc) => bloc.add(DisconnectRequested()), + expect: () => [ + isA(), + ], + ); -// bloc = WsprotocolBloc(repo: mockRepo); // Inject mock repository -// }); + blocTest( + 'This test the websocket when SendMessageRequest is added', + build: () => wsprotocolBloc, + act: (bloc) => bloc.add(SendMessageRequested("Send Message Requested")), + expect: () => [ + isA(), + ], + ); -// tearDown(() { -// bloc.close(); -// messageController.close(); -// }); + blocTest( + 'This test the websocket when MessageReceived is added', + build: () => wsprotocolBloc, + act: (bloc) => bloc.add(MessageReceived('Message Received')), + expect: () => [ + isA(), + ], + ); -// test('initial state is WsprotocolInitial', () { -// expect(bloc.state, WsprotocolInitial()); -// }); + blocTest( + 'This test the websocket when ConnectionErrorOccurred is added', + build: () => wsprotocolBloc, + act: (bloc) => bloc.add(ConnectionErrorOccurred("Connect Error Occurred")), + expect: () => [ + isA(), + ], + ); -// blocTest( -// 'emits [WsprotocolConnecting, WsprotocolConnected] when ConnectRequested is added', -// build: () { -// when(() => mockRepo.connect()).thenAnswer((_) {}); -// return bloc; -// }, -// act: (bloc) => bloc.add(ConnectRequested()), -// expect: () => [ -// WsprotocolConnecting(), -// WsprotocolConnected(messages: []), -// ], -// ); - -// blocTest( -// 'emits [WsprotocolError] when connection fails', -// build: () { -// when(() => mockRepo.connect()).thenThrow(Exception("Connection failed")); -// return bloc; -// }, -// act: (bloc) => bloc.add(ConnectRequested()), -// expect: () => [ -// WsprotocolConnecting(), -// WsprotocolError("Exception: Connection failed"), -// ], -// ); - -// blocTest( -// 'emits [WsprotocolConnected] when a message is received', -// build: () { -// return bloc; -// }, -// act: (bloc) { -// messageController.add("New message"); -// }, -// expect: () => [ -// WsprotocolConnected(messages: ["Received: New message"]), -// ], -// ); - -// blocTest( -// 'emits [WsprotocolDisconnected] when DisconnectRequested is added', -// build: () { -// when(() => mockRepo.disconnect()).thenAnswer((_) {}); -// return bloc; -// }, -// act: (bloc) => bloc.add(DisconnectRequested()), -// expect: () => [ -// WsprotocolDisconnected(), -// ], -// ); -// } + tearDown(() { + wsprotocolBloc.close(); + }); + }); +} From b6e199c4badd21998f0467542c5bcce120d47cb7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 16 Feb 2025 12:28:29 +0100 Subject: [PATCH 2/2] feat: added basic (working) unit tests for EventSocketsBloc --- .../bloc/event_sockets_bloc.dart | 18 +- .../repository/event_sockets_repo_impl.dart | 20 ++- .../bloc/event_sockets_bloc_test.dart | 154 +++++++----------- 3 files changed, 84 insertions(+), 108 deletions(-) diff --git a/lib/features/event_sockets/bloc/event_sockets_bloc.dart b/lib/features/event_sockets/bloc/event_sockets_bloc.dart index 0bf4cbf..463f064 100644 --- a/lib/features/event_sockets/bloc/event_sockets_bloc.dart +++ b/lib/features/event_sockets/bloc/event_sockets_bloc.dart @@ -8,7 +8,11 @@ import 'package:socket_probe/features/event_sockets/bloc/event_sockets_state.dar import 'package:socket_probe/features/event_sockets/data/repository/event_sockets_repo_impl.dart'; class EventSocketsBloc extends Bloc { - late EventSocketsRepoImpl _repository; + EventSocketsRepoImpl _repository = EventSocketsRepoImpl("wss://echo.websocket.events", { + "auth": {"token": "userToken"}, + "transports": ["websocket"], + "autoConnect": true, + }); StreamSubscription? _messageSubscription; final List _messages = []; @@ -36,9 +40,9 @@ class EventSocketsBloc extends Bloc { } _handleConnect(ConnectRequested event, Emitter emit) { - _repository = EventSocketsRepoImpl(event.socketUrl, event.sockedConfigurations); try { emit(EventSocketsConnecting()); + _repository = EventSocketsRepoImpl(event.socketUrl, event.sockedConfigurations); _repository.connectToSocket(); @@ -84,9 +88,13 @@ class EventSocketsBloc extends Bloc { } Future _onMessageReceived(MessageReceived event, Emitter emit) async { - // Log the incoming message. - _messages.add("Received: ${event.message}"); - emit(EventSocketsConnected(messages: List.from(_messages))); + try { + // Log the incoming message. + _messages.add("Received: ${event.message}"); + emit(EventSocketsConnected(messages: List.from(_messages))); + } catch (e) { + emit(EventSocketsError(error: e.toString())); + } } Future _onConnectionErrorOccurred(ConnectionErrorOccurred event, Emitter emit) async { diff --git a/lib/features/event_sockets/data/repository/event_sockets_repo_impl.dart b/lib/features/event_sockets/data/repository/event_sockets_repo_impl.dart index e5cc1ba..d0bb486 100644 --- a/lib/features/event_sockets/data/repository/event_sockets_repo_impl.dart +++ b/lib/features/event_sockets/data/repository/event_sockets_repo_impl.dart @@ -7,7 +7,7 @@ class EventSocketsRepoImpl { final String socketUrl; final dynamic socketOpts; - late sio.Socket socket; + sio.Socket? socket; // A broadcast controller lets multiple listeners receive the stream of messages. final StreamController _messageController = StreamController.broadcast(); @@ -21,23 +21,23 @@ class EventSocketsRepoImpl { // Connect to the Socket.IO server socket = sio.io(url, socketOpts); - socket.on('connect', (message) { + socket?.on('connect', (message) { // socket.subEvents(); _messageController.add(message); log('Connected to server'); }); - socket.onAny((event, data) { + socket?.onAny((event, data) { _messageController.add(data); log("event name: $event, event data: $data"); }); - socket.onError((data) { + socket?.onError((data) { _messageController.addError(data); log("connection error: $data"); }); - socket.onConnectError((data) { + socket?.onConnectError((data) { _messageController.addError(data); log("connection error: $data"); }); @@ -49,16 +49,18 @@ class EventSocketsRepoImpl { Stream get messages => _messageController.stream; void sendEventMessage(String event, [dynamic data]) { - if (socket.connected) { - socket.emit(event, data); + if (socket == null) return; + if (socket?.connected ?? false) { + socket?.emit(event, data); } else { throw Exception("WebSocket connection is not established. Call connect() first."); } } void disconnect() { - if (socket.connected) { - socket.disconnect(); + if (socket == null) return; + if (socket?.connected ?? false) { + socket?.disconnect(); log('disconnected from server'); } } diff --git a/test/event_sockets/bloc/event_sockets_bloc_test.dart b/test/event_sockets/bloc/event_sockets_bloc_test.dart index 61bee59..ac29a16 100644 --- a/test/event_sockets/bloc/event_sockets_bloc_test.dart +++ b/test/event_sockets/bloc/event_sockets_bloc_test.dart @@ -1,103 +1,69 @@ -// import 'dart:async'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:socket_probe/features/event_sockets/bloc/event_sockets_bloc.dart'; +import 'package:socket_probe/features/event_sockets/bloc/event_sockets_event.dart'; +import 'package:socket_probe/features/event_sockets/bloc/event_sockets_state.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:bloc_test/bloc_test.dart'; -// import 'package:mocktail/mocktail.dart'; -// import 'package:socket_probe/features/event_sockets/bloc/event_sockets_bloc.dart'; -// import 'package:socket_probe/features/event_sockets/bloc/event_sockets_event.dart'; -// import 'package:socket_probe/features/event_sockets/bloc/event_sockets_state.dart'; -// import 'package:socket_probe/features/event_sockets/data/repository/event_sockets_repo_impl.dart'; +void main() { + group(EventSocketsBloc, () { + late EventSocketsBloc eventSocketsBloc; -// // Mock repository class -// class MockEventSocketsRepo extends Mock implements EventSocketsRepoImpl {} + setUp(() { + eventSocketsBloc = EventSocketsBloc(); + }); -// void main() { -// late EventSocketsBloc eventSocketsBloc; -// late MockEventSocketsRepo mockRepository; -// late StreamController messageController; + blocTest( + 'This test the websocket connection when ConnectedRequest is added', + build: () => eventSocketsBloc, + act: (bloc) => bloc.add( + ConnectRequested(socketUrl: 'wss://echo.websocket.events', sockedConfigurations: { + "auth": {"token": "userToken"}, + "transports": ["websocket"], + "autoConnect": true, + }), + ), + expect: () => [ + isA(), + isA(), + ], + ); -// setUp(() { -// mockRepository = MockEventSocketsRepo(); -// messageController = StreamController.broadcast(); + blocTest( + 'This test the websocket connection when DisconnectRequested is added', + build: () => eventSocketsBloc, + act: (bloc) => bloc.add(DisconnectRequested()), + expect: () => [isA()], + ); -// when(() => mockRepository.messages).thenAnswer((_) => messageController.stream); -// when(() => mockRepository.connectToSocket()).thenAnswer((_) { -// messageController.add('Connected'); // Simulating successful connection -// }); -// when(() => mockRepository.disconnect()).thenReturn(null); -// when(() => mockRepository.sendEventMessage(any(), any())).thenReturn(null); + blocTest( + 'This test the websocket connection when SendMessageRequested is added', + build: () => eventSocketsBloc, + act: (bloc) => bloc.add( + SendMessageRequested(message: "Send Message Requested", event: "message.event"), + ), + expect: () => [isA()], + ); -// eventSocketsBloc = EventSocketsBloc(); -// // eventSocketsBloc._repository = mockRepository; -// }); + blocTest( + 'This test the websocket connection when MessageReceived is added', + build: () => eventSocketsBloc, + act: (bloc) => bloc.add( + MessageReceived(message: "Message Received"), + ), + expect: () => [isA()], + ); -// tearDown(() { -// eventSocketsBloc.close(); -// messageController.close(); -// }); + blocTest( + 'This test the websocket connection when ConnectionErrorOccurred is added', + build: () => eventSocketsBloc, + act: (bloc) => bloc.add( + ConnectionErrorOccurred(message: "Connection Error Occurred"), + ), + expect: () => [isA()], + ); -// test('initial state should be EventSocketsInitial', () { -// expect(eventSocketsBloc.state, equals(EventSocketsInitial())); -// }); + tearDown(() => eventSocketsBloc.close()); -// blocTest( -// 'emits [EventSocketsConnecting, EventSocketsConnected] when ConnectRequested is added', -// build: () => eventSocketsBloc, -// act: (bloc) => bloc.add(ConnectRequested(socketUrl: 'ws://test-url', sockedConfigurations: {})), -// expect: () => [ -// EventSocketsConnecting(), -// EventSocketsConnected(messages: []), -// ], -// verify: (_) { -// verify(() => mockRepository.connectToSocket()).called(1); -// }, -// ); - -// blocTest( -// 'emits [EventSocketsDisconnected] when DisconnectRequested is added', -// build: () => eventSocketsBloc, -// act: (bloc) => bloc.add(DisconnectRequested()), -// expect: () => [ -// EventSocketsDisconnected(), -// ], -// verify: (_) { -// verify(() => mockRepository.disconnect()).called(1); -// }, -// ); - -// blocTest( -// 'emits [EventSocketsConnected] when a message is received', -// build: () => eventSocketsBloc, -// act: (bloc) { -// messageController.add('New Event Message'); -// bloc.add(MessageReceived(message: 'New Event Message')); -// }, -// expect: () => [ -// EventSocketsConnected(messages: ['Received: New Event Message']), -// ], -// ); - -// blocTest( -// 'emits [EventSocketsConnected] when SendMessageRequested is added', -// build: () => eventSocketsBloc, -// act: (bloc) => bloc.add(SendMessageRequested(event: 'testEvent', message: 'testMessage')), -// expect: () => [ -// EventSocketsConnected(messages: ['Sent: testMessage']), -// ], -// verify: (_) { -// verify(() => mockRepository.sendEventMessage('testEvent', 'testMessage')).called(1); -// }, -// ); - -// blocTest( -// 'emits [EventSocketsError] when a connection error occurs', -// build: () => eventSocketsBloc, -// act: (bloc) { -// messageController.addError('Socket connection failed'); -// bloc.add(ConnectionErrorOccurred(message: 'Socket connection failed')); -// }, -// expect: () => [ -// EventSocketsError(error: 'Socket connection failed'), -// ], -// ); -// } + // end of test + }); +}