Skip to content

Commit d914b3c

Browse files
committed
refactor(llc): simplify WebSocket test setup and improve test utilities
This commit refactors the WebSocket testing utilities to simplify test setup and enhance maintainability. The `ws_test_helpers.dart` file has been removed, and its functionality is replaced by a new, more streamlined `whenListenWebSocket` function in `web_socket_mocks.dart`. This function encapsulates the mock setup for `WebSocketChannel`, including handling authentication responses. Key changes: - **Replaced `WsTestConnection` with `whenListenWebSocket`**: Simplified the setup for mocking WebSocket channels in tests. - **Improved test token generation**: Replaced a hardcoded JWT with a `generateTestUserToken` function for creating test tokens dynamically. - **Relocated `api_mocker_mixin.dart`**: Moved the file for better organization within the test utilities. - **Removed unused mocks**: Cleaned up the `mocks.dart` file by removing `MockFeedsRepository`, `MockFeedsClient`, `MockWebSocketClient`, `MockWebSocketSink`, and `FakeFeedsClient`. - **Updated test implementation**: All tests now use the new `whenListenWebSocket` helper and dynamic token generation, reducing boilerplate and improving clarity.
1 parent 27e9753 commit d914b3c

File tree

7 files changed

+91
-125
lines changed

7 files changed

+91
-125
lines changed

packages/stream_feeds/test/client/feeds_client_impl_test.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import '../test_utils.dart';
66

77
void main() {
88
test('Create a feeds client', () {
9+
const user = User(id: 'userId');
10+
final token = generateTestUserToken(user.id);
11+
912
final client = StreamFeedsClient(
1013
apiKey: 'apiKey',
11-
user: const User(id: 'userId'),
12-
tokenProvider: TokenProvider.static(UserToken(testToken)),
14+
user: user,
15+
tokenProvider: TokenProvider.static(token),
1316
);
1417

1518
expect(client, isA<StreamFeedsClientImpl>());

packages/stream_feeds/test/test_utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ export 'test_utils/testers/feed_tester.dart';
1111
export 'test_utils/testers/follow_list_tester.dart';
1212
export 'test_utils/testers/poll_list_tester.dart';
1313
export 'test_utils/testers/poll_vote_list_tester.dart';
14-
export 'test_utils/ws_test_helpers.dart';
14+
export 'test_utils/web_socket_mocks.dart';

packages/stream_feeds/test/test_utils/testers/api_mocker_mixin.dart renamed to packages/stream_feeds/test/test_utils/api_mocker_mixin.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:mocktail/mocktail.dart';
22
import 'package:stream_feeds/stream_feeds.dart';
33

4-
import '../mocks.dart';
4+
import 'mocks.dart';
55

66
/// Mixin for test utilities that need to mock and verify API calls.
77
///
Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,24 @@
1+
import 'dart:convert';
2+
13
import 'package:mocktail/mocktail.dart';
2-
import 'package:stream_feeds/src/client/feeds_client_impl.dart';
3-
import 'package:stream_feeds/src/repository/feeds_repository.dart';
4-
import 'package:stream_feeds/stream_feeds.dart';
54
import 'package:stream_feeds/stream_feeds.dart' as api;
65
import 'package:web_socket_channel/web_socket_channel.dart';
76

8-
class MockFeedsRepository extends Mock implements FeedsRepository {}
9-
10-
class MockFeedsClient extends Mock implements StreamFeedsClient {}
11-
12-
class MockWebSocketClient extends Mock implements StreamWebSocketClient {}
13-
147
class MockDefaultApi extends Mock implements api.DefaultApi {}
158

169
class MockWebSocketChannel extends Mock implements WebSocketChannel {}
1710

18-
class MockWebSocketSink extends Mock implements WebSocketSink {}
11+
api.UserToken generateTestUserToken(String userId) {
12+
String b64UrlNoPad(Object jsonObj) {
13+
final bytes = utf8.encode(jsonEncode(jsonObj));
14+
return base64Url.encode(bytes).replaceAll('=', '');
15+
}
1916

20-
class FakeFeedsClient extends Fake implements StreamFeedsClientImpl {
21-
FakeFeedsClient({
22-
User? user,
23-
}) : user = user ?? fakeUser;
17+
final header = {'alg': 'none', 'typ': 'JWT'};
18+
final payload = {'user_id': userId};
2419

25-
@override
26-
final User user;
20+
final jwt = '${b64UrlNoPad(header)}.${b64UrlNoPad(payload)}.';
21+
// trailing dot = empty signature (valid for alg=none)
2722

28-
@override
29-
final EventEmitter events = MutableEventEmitter();
23+
return api.UserToken(jwt);
3024
}
31-
32-
const fakeUser = User(
33-
id: 'user_id',
34-
name: 'user_name',
35-
);
36-
37-
const testToken =
38-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.hZ59SWtp_zLKVV9ShkqkTsCGi_jdPHly7XNCf5T_Ev0';

packages/stream_feeds/test/test_utils/testers/base_tester.dart

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import 'package:meta/meta.dart';
55
import 'package:stream_feeds/stream_feeds.dart';
66
import 'package:test/test.dart' as test;
77

8+
import '../api_mocker_mixin.dart';
89
import '../mocks.dart';
9-
import '../ws_test_helpers.dart';
10-
import 'api_mocker_mixin.dart';
10+
import '../web_socket_mocks.dart';
1111

1212
/// Factory function signature for creating tester instances.
1313
///
@@ -81,22 +81,12 @@ Future<T> createTester<T extends BaseTester<Object?>>({
8181
required MockWebSocketChannel webSocketChannel,
8282
required T Function(StreamController<Object>) create,
8383
}) async {
84-
// Create WebSocket components
84+
// Create WebSocket stream controller
8585
final wsStreamController = StreamController<Object>();
86-
final webSocketSink = MockWebSocketSink();
86+
test.addTearDown(wsStreamController.close); // Close controller after test
8787

88-
// Register automatic cleanup
89-
test.addTearDown(() async {
90-
await webSocketSink.close();
91-
await wsStreamController.close();
92-
});
93-
94-
// Setup WebSocket connection
95-
WsTestConnection(
96-
wsStreamController: wsStreamController,
97-
webSocketSink: webSocketSink,
98-
webSocketChannel: webSocketChannel,
99-
).setUp();
88+
// Set up WebSocket channel mocks
89+
whenListenWebSocket(webSocketChannel, wsStreamController);
10090

10191
// Connect client
10292
await client.connect();
@@ -141,7 +131,7 @@ void testWithTester<S, T extends BaseTester<S>>(
141131
() async {
142132
await _runZonedGuarded(() async {
143133
const user = User(id: 'luke_skywalker');
144-
final userToken = UserToken(testToken);
134+
final userToken = generateTestUserToken(user.id);
145135

146136
final feedsApi = MockDefaultApi();
147137
final webSocketChannel = MockWebSocketChannel();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
4+
import 'package:mocktail/mocktail.dart';
5+
import 'package:stream_feeds/src/ws/events/events.dart';
6+
import 'package:web_socket_channel/web_socket_channel.dart';
7+
8+
class _MockWebSocketSink extends Mock implements WebSocketSink {}
9+
10+
/// Sets up WebSocket channel mocks for testing.
11+
///
12+
/// Configures [webSocketChannel] to use [wsStreamController] for its stream
13+
/// when the channel's stream is listened to. Also creates a mock sink for
14+
/// sending messages and handles authentication by automatically responding
15+
/// with a health check event when a token is sent.
16+
///
17+
/// Example:
18+
/// ```dart
19+
/// final wsStreamController = StreamController<Object>();
20+
/// whenListenWebSocket(webSocketChannel, wsStreamController);
21+
/// ```
22+
void whenListenWebSocket(
23+
WebSocketChannel webSocketChannel,
24+
StreamController<Object> wsStreamController,
25+
) {
26+
final webSocketSink = _MockWebSocketSink();
27+
28+
when(
29+
webSocketSink.close,
30+
).thenAnswer((_) => Future<void>.value());
31+
32+
when(
33+
() => webSocketChannel.ready,
34+
).thenAnswer((_) => Future<void>.value());
35+
36+
when(
37+
() => webSocketChannel.stream,
38+
).thenAnswer((_) => wsStreamController.stream);
39+
40+
when(
41+
() => webSocketChannel.sink,
42+
).thenAnswer((_) => webSocketSink);
43+
44+
// Handle authentication: when a token is sent, respond with health check
45+
when(
46+
() => webSocketSink.add(any<Object>()),
47+
).thenAnswer((invocation) {
48+
final event = jsonDecode(
49+
invocation.positionalArguments.first as String,
50+
) as Map<String, dynamic>;
51+
52+
if (event['token'] != null) {
53+
return wsStreamController.add(
54+
jsonEncode(
55+
HealthCheckEvent(
56+
connectionId: 'connectionId',
57+
createdAt: DateTime.now(),
58+
custom: const {},
59+
type: 'health.check',
60+
),
61+
),
62+
);
63+
}
64+
});
65+
}

packages/stream_feeds/test/test_utils/ws_test_helpers.dart

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)