Skip to content

Commit

Permalink
fix: ✅ Fix side effects on unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Chralu committed Nov 28, 2024
1 parent 1b05acc commit 709362f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 67 deletions.
3 changes: 3 additions & 0 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class DeeplinkRpcClient {

if (runningRequest == null) return;

if (runningRequest.completer.isCompleted) return;
runningRequest.completer.complete(response);

_runningRequests.remove(response.id);
Expand Down Expand Up @@ -190,6 +191,8 @@ class _DeeplinkRpcResponseReceiver
e,
stack,
);
if (e is DeeplinkRpcFailure) rethrow;

throw const DeeplinkRpcResult.failure(
failure: DeeplinkRpcFailure(
code: DeeplinkRpcFailure.kServerError,
Expand Down
169 changes: 102 additions & 67 deletions test/client_server_communication_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:deeplink_rpc/deeplink_rpc.dart';
import 'package:deeplink_rpc/src/codec.dart';
import 'package:deeplink_rpc/src/url_launcher.dart';
Expand All @@ -13,27 +15,46 @@ class BrokenRPCCodec implements DeeplinkRpcCodec {
}

class MockUrlLauncher implements UrlLauncher {
MockUrlLauncher({
required Future<bool> Function(Uri url) launchUrl,
}) : _launchUrl = launchUrl;
MockUrlLauncher() : launchUrlAction = _defaultLaunchUrl;

static Future<bool> _defaultLaunchUrl(Uri _, {Logger? logger}) async => false;

final Future<bool> Function(Uri url) _launchUrl;
Future<bool> Function(Uri url, {Logger? logger}) launchUrlAction;

@override
Future<bool> canLaunchUrl(Uri url) async => true;

@override
Future<bool> launchUrl(Uri url, {Logger? logger}) async => _launchUrl(url);
Future<bool> launchUrl(Uri url, {Logger? logger}) async =>
launchUrlAction(url);
}

class Context {
Context({
DeeplinkRpcCodec? clientCodec,
DeeplinkRpcCodec? serverCodec,
}) {
clientUrlLauncherMock = MockUrlLauncher();
client = DeeplinkRpcClient(
urlLauncher: clientUrlLauncherMock,
codec: clientCodec,
);
serverUrlLauncherMock = MockUrlLauncher();
server = DeeplinkRpcServer(
urlLauncher: serverUrlLauncherMock,
codec: serverCodec,
);
}

late final MockUrlLauncher clientUrlLauncherMock;
late final DeeplinkRpcClient client;
late final MockUrlLauncher serverUrlLauncherMock;
late final DeeplinkRpcServer server;
}

void main() {
const defaultTimeout = Duration(milliseconds: 1);

late MockUrlLauncher clientUrlLauncherMock;
late DeeplinkRpcClient client;
late MockUrlLauncher serverUrlLauncherMock;
late DeeplinkRpcServer server;

bool _isSameOrigin(Uri uri1, Uri uri2) {
return uri1.scheme == uri2.scheme &&
uri1.host == uri2.host &&
Expand All @@ -42,59 +63,67 @@ void main() {
uri1.fragment == uri2.fragment;
}

void _setupClientAndServer({
Context _setupClientAndServer({
DeeplinkRpcCodec? clientCodec,
DeeplinkRpcCodec? serverCodec,
String serverBasePath = 'serverapp://server.app/request_endpoint',
String clientBasePath = 'clientapp://client.app/reply_endpoint',
}) {
final serverClient = Context(
clientCodec: clientCodec,
serverCodec: serverCodec,
);

final serverBaseUri = Uri.parse(serverBasePath);
clientUrlLauncherMock = MockUrlLauncher(
launchUrl: (uri) async {
if (!_isSameOrigin(uri, serverBaseUri)) {
return false;
}
await server.handle(
serverClient.clientUrlLauncherMock.launchUrlAction = (
uri, {
Logger? logger,
}) async {
if (!_isSameOrigin(uri, serverBaseUri)) {
return false;
}
unawaited(
serverClient.server.handle(
Uri(
path: uri.path,
query: uri.query,
),
);
return true;
},
);
client = DeeplinkRpcClient(
urlLauncher: clientUrlLauncherMock,
codec: clientCodec,
);
),
);
return true;
};

final clientBaseUri = Uri.parse(clientBasePath);
serverUrlLauncherMock = MockUrlLauncher(
launchUrl: (uri) async {
if (!_isSameOrigin(uri, clientBaseUri)) {
return false;
}
serverClient.serverUrlLauncherMock.launchUrlAction = (
uri, {
Logger? logger,
}) async {
if (!_isSameOrigin(uri, clientBaseUri)) {
return false;
}

return client.handleResponse(
Uri(
path: uri.path,
query: uri.query,
),
);
},
);
server = DeeplinkRpcServer(
urlLauncher: serverUrlLauncherMock,
);
serverClient.client.handleResponse(
Uri(
path: uri.path,
query: uri.query,
),
);
return true;
};

return serverClient;
}

void _setupClientWithNoServer() {
clientUrlLauncherMock = MockUrlLauncher(
launchUrl: (uri) async {
return false;
},
);
client = DeeplinkRpcClient(
urlLauncher: clientUrlLauncherMock,
);
Context _setupClientWithNoServer() {
final serverClient = Context();

serverClient.clientUrlLauncherMock.launchUrlAction = (
uri, {
Logger? logger,
}) async =>
false;

return serverClient;
}

for (final setup in [
Expand Down Expand Up @@ -138,11 +167,11 @@ void main() {
test(
'Client should receive a success response when call is valid (${setup.name})',
() async {
_setupClientAndServer(
final context = _setupClientAndServer(
clientBasePath: setup.replyUrl,
serverBasePath: setup.requestUrl,
);
server.registerHandler(
context.server.registerHandler(
DeeplinkRpcRequestHandler(
route: DeeplinkRpcRoute(setup.serverRoute),
handle: (request) => {
Expand All @@ -152,7 +181,7 @@ void main() {
),
);

final response = await client.send(
final response = await context.client.send(
timeout: defaultTimeout,
request: DeeplinkRpcRequest(
requestUrl: setup.requestUrl,
Expand All @@ -175,8 +204,8 @@ void main() {

test('Client should receive a failure when called deeplink has no receiver',
() async {
_setupClientWithNoServer();
final response = await client.send(
final context = _setupClientWithNoServer();
final response = await context.client.send(
timeout: defaultTimeout,
request: DeeplinkRpcRequest(
requestUrl: 'serverapp://server.app/request_endpoint',
Expand All @@ -195,24 +224,30 @@ void main() {
test(
'Client should receive a timeout failure when call is longer than expected',
() async {
_setupClientAndServer();
final context = _setupClientAndServer();

server.registerHandler(
context.server.registerHandler(
DeeplinkRpcRequestHandler(
route: const DeeplinkRpcRoute('request_endpoint'),
route: const DeeplinkRpcRoute('/request_endpoint'),
handle: (request) async {
await Future.delayed(const Duration(milliseconds: 1));
return {};
return {
'responseString': 'aString',
'responseInt': 4,
};
},
),
);

final response = await client.send(
final response = await context.client.send(
timeout: const Duration(microseconds: 1),
request: DeeplinkRpcRequest(
requestUrl: 'serverapp://server.app/request_endpoint',
replyUrl: 'clientapp://client.app/reply_endpoint',
params: {},
params: {
'clientProp1': 'value1',
'clientProp2': 2,
},
),
);
expect(
Expand All @@ -227,18 +262,18 @@ void main() {
test(
"Client should not receive response when call format is invalid (because the server doesn't know at which URL to send the response)",
() async {
_setupClientAndServer(
final context = _setupClientAndServer(
clientCodec: BrokenRPCCodec(),
);

server.registerHandler(
context.server.registerHandler(
DeeplinkRpcRequestHandler(
route: const DeeplinkRpcRoute('request_endpoint'),
route: const DeeplinkRpcRoute('/request_endpoint'),
handle: (request) async => {},
),
);

final response = await client.send(
final response = await context.client.send(
timeout: defaultTimeout,
request: DeeplinkRpcRequest(
requestUrl: 'serverapp://server.app/request_endpoint',
Expand All @@ -257,9 +292,9 @@ void main() {

test('Client should receive a failure when server endpoint does not exist',
() async {
_setupClientAndServer();
final context = _setupClientAndServer();

final response = await client.send(
final response = await context.client.send(
timeout: defaultTimeout,
request: DeeplinkRpcRequest(
requestUrl: 'serverapp://server.app/unknown_endpoint',
Expand Down

0 comments on commit 709362f

Please sign in to comment.