Skip to content

Commit 5c01453

Browse files
Create a fake WebSocket implementation (#1200)
* Create a fake WebSocket implementation * Better example * Update pkgs/web_socket/lib/testing.dart Co-authored-by: Nate Bosch <[email protected]> --------- Co-authored-by: Nate Bosch <[email protected]>
1 parent 4d2f9f9 commit 5c01453

File tree

5 files changed

+189
-1
lines changed

5 files changed

+189
-1
lines changed

pkgs/web_socket/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.1.4
2+
3+
- Add a `fakes` function that returns a pair of `WebSocket`s useful in
4+
testing.
5+
16
## 0.1.3
27

38
- Bring the behavior in line with the documentation by throwing
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:typed_data';
7+
8+
import '../web_socket.dart';
9+
import 'utils.dart';
10+
import 'web_socket.dart';
11+
12+
class FakeWebSocket implements WebSocket {
13+
late FakeWebSocket _other;
14+
15+
final String _protocol;
16+
final _events = StreamController<WebSocketEvent>();
17+
18+
FakeWebSocket(this._protocol);
19+
20+
@override
21+
Future<void> close([int? code, String? reason]) async {
22+
if (_events.isClosed) {
23+
throw WebSocketConnectionClosed();
24+
}
25+
26+
checkCloseCode(code);
27+
checkCloseReason(reason);
28+
29+
unawaited(_events.close());
30+
if (!_other._events.isClosed) {
31+
_other._events.add(CloseReceived(code ?? 1005, reason ?? ''));
32+
unawaited(_other._events.close());
33+
}
34+
}
35+
36+
@override
37+
Stream<WebSocketEvent> get events => _events.stream;
38+
39+
@override
40+
String get protocol => _protocol;
41+
42+
@override
43+
void sendBytes(Uint8List b) {
44+
if (_events.isClosed) {
45+
throw WebSocketConnectionClosed();
46+
}
47+
if (_other._events.isClosed) return;
48+
_other._events.add(BinaryDataReceived(b));
49+
}
50+
51+
@override
52+
void sendText(String s) {
53+
if (_events.isClosed) {
54+
throw WebSocketConnectionClosed();
55+
}
56+
if (_other._events.isClosed) return;
57+
_other._events.add(TextDataReceived(s));
58+
}
59+
}
60+
61+
/// Create a pair of fake [WebSocket]s that are connected to each other.
62+
///
63+
/// Sending a message on one [WebSocket] will result in that same message being
64+
/// received by the other.
65+
///
66+
/// This can be useful in constructing tests.
67+
///
68+
/// For example:
69+
///
70+
/// ```dart
71+
/// import 'dart:async';
72+
///
73+
/// import 'package:test/test.dart';
74+
/// import 'package:web_socket/src/web_socket.dart';
75+
/// import 'package:web_socket/testing.dart';
76+
/// import 'package:web_socket/web_socket.dart';
77+
///
78+
/// Future<void> fakeTimeServer(WebSocket webSocket, String time) async {
79+
/// await webSocket.events.forEach((event) {
80+
/// switch (event) {
81+
/// case TextDataReceived():
82+
/// case BinaryDataReceived():
83+
/// webSocket.sendText(time);
84+
/// case CloseReceived():
85+
/// }
86+
/// });
87+
/// }
88+
///
89+
/// Future<DateTime> getTime(WebSocket webSocket) async {
90+
/// webSocket.sendText('');
91+
/// final time = switch (await webSocket.events.first) {
92+
/// TextDataReceived(:final text) => DateTime.parse(text),
93+
/// _ => throw Exception('unexpected response')
94+
/// };
95+
/// await webSocket.close();
96+
/// return time;
97+
/// }
98+
///
99+
/// void main() async {
100+
/// late WebSocket client;
101+
/// late WebSocket server;
102+
///
103+
/// setUp(() {
104+
/// (client, server) = fakes();
105+
/// });
106+
///
107+
/// test('test valid time', () async {
108+
/// unawaited(fakeTimeServer(server, '2024-05-15T01:18:10.456Z'));
109+
/// expect(
110+
/// await getTime(client),
111+
/// DateTime.parse('2024-05-15T01:18:10.456Z'));
112+
/// });
113+
/// }
114+
/// ```
115+
(WebSocket, WebSocket) fakes({String protocol = ''}) {
116+
final peer1 = FakeWebSocket(protocol);
117+
final peer2 = FakeWebSocket(protocol);
118+
119+
peer1._other = peer2;
120+
peer2._other = peer1;
121+
122+
return (peer1, peer2);
123+
}

pkgs/web_socket/lib/testing.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'src/fake_web_socket.dart' show fakes;

pkgs/web_socket/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: >-
33
Any easy-to-use library for communicating with WebSockets
44
that has multiple implementations.
55
repository: https://github.com/dart-lang/http/tree/master/pkgs/web_socket
6-
version: 0.1.3
6+
version: 0.1.4
77

88
environment:
99
sdk: ^3.3.0
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:web_socket/src/fake_web_socket.dart';
6+
import 'package:web_socket/web_socket.dart';
7+
import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart';
8+
9+
/// Forward data received from [from] to [to].
10+
void proxy(WebSocket from, WebSocket to) {
11+
from.events.listen((event) {
12+
try {
13+
switch (event) {
14+
case TextDataReceived(:final text):
15+
to.sendText(text);
16+
case BinaryDataReceived(:final data):
17+
to.sendBytes(data);
18+
case CloseReceived(:var code, :final reason):
19+
if (code != null && (code < 3000 || code > 4999)) {
20+
code = null;
21+
}
22+
to.close(code, reason);
23+
}
24+
} on WebSocketConnectionClosed {
25+
// `to` may have been closed locally so ignore failures to forward the
26+
// data.
27+
}
28+
});
29+
}
30+
31+
/// Create a bidirectional proxy relationship between [a] and [b].
32+
///
33+
/// That means that events received by [a] will be forwarded to [b] and
34+
/// vise-versa.
35+
void bidirectionalProxy(WebSocket a, WebSocket b) {
36+
proxy(a, b);
37+
proxy(b, a);
38+
}
39+
40+
void main() {
41+
// In order to use `testAll`, we need to provide a method that will connect
42+
// to a real WebSocket server.
43+
//
44+
// The approach is to connect to the server with a real WebSocket and forward
45+
// the data received by that data to one of the fakes.
46+
//
47+
// Like:
48+
//
49+
// 'hello' sendText('hello') TextDataReceived('hello')
50+
// [Server] -> [realClient] -> [FakeServer] -> [fakeClient]
51+
Future<WebSocket> connect(Uri url, {Iterable<String>? protocols}) async {
52+
final realClient = await WebSocket.connect(url, protocols: protocols);
53+
final (fakeServer, fakeClient) = fakes(protocol: realClient.protocol);
54+
bidirectionalProxy(realClient, fakeServer);
55+
return fakeClient;
56+
}
57+
58+
testAll(connect);
59+
}

0 commit comments

Comments
 (0)