Skip to content

Commit 9e3953e

Browse files
graphql_flutter: add integration testing
Signed-off-by: Vincenzo Palazzo <[email protected]>
1 parent f7bd1fc commit 9e3953e

File tree

11 files changed

+353
-15
lines changed

11 files changed

+353
-15
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: "graphql_flutter: integration testing "
2+
on: [push, pull_request]
3+
jobs:
4+
integration_tests:
5+
runs-on: ubuntu-20.04
6+
steps:
7+
- uses: actions/checkout@v2
8+
- uses: subosito/flutter-action@v1
9+
- run: flutter pub get
10+
- run: chromedriver --port=4444 &
11+
- name: graphql_chat intergration testing
12+
run: |
13+
cd packages/graphql_flutter/example/graphql_chat
14+
flutter pub dev
15+
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/intergration_test.dart -d chrome

packages/graphql/lib/src/links/websocket_link/websocket_client.dart

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,6 @@ class SocketSubProtocol {
159159
static const String graphqlTransportWs = GraphQLProtocol.graphqlTransportWs;
160160
}
161161

162-
/// graphql-ws: The new (not to be confused with the graphql-ws library).
163-
/// NB. This protocol is it no longer maintained, please consider
164-
/// to use `SocketSubProtocol.graphqlTransportWs`.
165-
static const String graphqlWs = GraphQLProtocol.graphqlWs;
166-
167-
/// graphql-transport-ws: New ws protocol used by most Apollo Server instances
168-
/// with subscriptions enabled use this library.
169-
/// N.B: not to be confused with the graphql-ws library that implement the
170-
/// old ws protocol.
171-
static const String graphqlTransportWs = GraphQLProtocol.graphqlTransportWs;
172-
}
173-
174162
/// ALL protocol supported by the library
175163
class GraphQLProtocol {
176164
GraphQLProtocol._();

packages/graphql_flutter/example/graphql_chat/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
CC=flutter
22
FMT=format
33
PATH=
4-
default: get fmt check fmt
4+
5+
default: get fmt check
56

67
get:
78
$(CC) pub get
@@ -23,4 +24,4 @@ run:
2324
$(CC) run -d linux
2425

2526
dep_upgrade:
26-
$(CC) pub upgrade --major-versions
27+
$(CC) pub upgrade --major-versions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// Client Provider is a utils class that contains in a single place
2+
/// all the client used to ran the integration testing with all the
3+
/// servers.
4+
///
5+
/// The test supported in this class for the moment are:
6+
/// - GraphQl Client Chat: https://github.com/vincenzopalazzo/keep-safe-graphql
7+
///
8+
/// author: https://github.com/vincenzopalazzo
9+
10+
/// ClientProvider is a singleton implementation that contains
11+
/// all the client supported to implement the integration testing.
12+
import 'package:graphql_flutter/graphql_flutter.dart';
13+
14+
class ClientProvider {
15+
static ClientProvider? _instance;
16+
17+
GraphQLClient? _chatApp;
18+
19+
ClientProvider._internal();
20+
21+
factory ClientProvider() {
22+
_instance ??= ClientProvider._internal();
23+
return _instance!;
24+
}
25+
26+
/// Init the client regarding the chat app server
27+
/// crete only a single instance of it.
28+
GraphQLClient chatAppClient(
29+
{GraphQLCache? cache, bool forceRecreation = false}) {
30+
if (_chatApp == null || forceRecreation) {
31+
final HttpLink httpLink = HttpLink(
32+
'https://api.chat.graphql-flutter.dev/graphql',
33+
defaultHeaders: {
34+
"Accept": "application/json",
35+
"Access-Control_Allow_Origin": "*",
36+
},
37+
);
38+
var wsLink = WebSocketLink(
39+
'ws://api.chat.graphql-flutter.dev/graphql',
40+
config: const SocketClientConfig(
41+
inactivityTimeout: Duration(seconds: 40),
42+
),
43+
subProtocol: GraphQLProtocol.graphqlTransportWs,
44+
);
45+
final Link link = httpLink.split(
46+
(request) => request.isSubscription,
47+
wsLink,
48+
httpLink,
49+
);
50+
if (!forceRecreation) {
51+
_chatApp = GraphQLClient(link: link, cache: cache ?? GraphQLCache());
52+
} else {
53+
return GraphQLClient(link: link, cache: cache ?? GraphQLCache());
54+
}
55+
}
56+
return _chatApp!;
57+
}
58+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/// Integration test regarding the graphq_flutter widget
2+
///
3+
/// In this particular test suite we implement a battery of test
4+
/// to make sure that all the operation on a widget are done
5+
/// correctly.
6+
///
7+
/// More precise in this file all the test are regarding the web socket and
8+
/// the subscription.
9+
///
10+
/// Trying to reproduce the problems caused by the following issue
11+
/// - Subscription not receive update
12+
/// - https://github.com/zino-hofmann/graphql-flutter/issues/1163
13+
/// - https://github.com/zino-hofmann/graphql-flutter/issues/1162
14+
///
15+
/// author: https://github.com/vincenzopalazzo
16+
import 'package:flutter_test/flutter_test.dart';
17+
import 'package:graphql_flutter/graphql_flutter.dart';
18+
import 'package:integration_test/integration_test.dart';
19+
import 'package:logger/logger.dart';
20+
import 'clients/ClientProvider.dart';
21+
import 'model/chat_app_model.dart';
22+
import 'widget/MockAppView.dart' as app;
23+
import 'widget/list_view_query.dart';
24+
import 'widget/list_view_subscription.dart';
25+
26+
/// Configure the test to check if the ws `GraphQLProtocol.graphqlTransportWs` receive
27+
/// update correctly.
28+
Future<void> configureTestForSimpleSubscriptionUpdate(dynamic tester) async {
29+
var logger = Logger();
30+
31+
/// build a simple client with a in memory
32+
var client = ClientProvider().chatAppClient();
33+
app.main(client: client, childToTest: ListChatSubscriptionView());
34+
await tester.pumpAndSettle();
35+
var listChats = await client.query(QueryOptions(document: gql(r"""
36+
query {
37+
getChats {
38+
__typename
39+
id
40+
description
41+
name
42+
}
43+
}
44+
""")));
45+
logger.d("Exception: ${listChats.exception}");
46+
expect(listChats.hasException, isFalse);
47+
logger.i("Chats contains inside the server: ${listChats.data}");
48+
var chatName = "graphql_flutter test chat ${DateTime.now()}";
49+
var description = "graphql_flutter integration test in ${DateTime.now()}";
50+
await client.mutate(MutationOptions(document: gql(r"""
51+
mutation CreateChat($description: String!, $name: String!){
52+
createChat(description: $description, name: $name) {
53+
__typename
54+
name
55+
}
56+
}
57+
"""), variables: {
58+
"name": chatName,
59+
"description": description,
60+
}));
61+
62+
//find new item in the list
63+
await tester.pump(const Duration(seconds: 10));
64+
final subscriptionItem = find.text(chatName);
65+
expect(subscriptionItem, findsOneWidget);
66+
}
67+
68+
/// Configure the test to check if we receive all the element from the graphql
69+
Future<void> configureTestForSimpleQuery(dynamic tester) async {
70+
var logger = Logger();
71+
72+
/// build a simple client with a in memory
73+
var client = ClientProvider().chatAppClient();
74+
app.main(client: client, childToTest: ListChatQueryView());
75+
await tester.pumpAndSettle();
76+
await tester.pump(const Duration(seconds: 10));
77+
var listChats = await client.query(QueryOptions(
78+
document: gql(r"""
79+
query {
80+
getChats {
81+
__typename
82+
id
83+
description
84+
name
85+
}
86+
}
87+
"""),
88+
parserFn: (Map<String, dynamic> json) {
89+
var rawList = List.of(json["getChats"] as List<dynamic>);
90+
return rawList
91+
.map((jsonChat) => Chat.fromJSON(jsonChat as Map<String, dynamic>))
92+
.toList();
93+
}));
94+
logger.d("Exception: ${listChats.exception}");
95+
expect(listChats.hasException, isFalse);
96+
logger.i("Chats contains inside the server: ${listChats.data}");
97+
var chats = listChats.parsedData as List<Chat>;
98+
for (var chat in chats) {
99+
//find new item in the list
100+
final subscriptionItem = find.text(chat.name);
101+
102+
/// Finds not only one but more than one
103+
expect(subscriptionItem, findsWidgets);
104+
}
105+
}
106+
107+
class CustomBindings extends AutomatedTestWidgetsFlutterBinding {
108+
@override
109+
bool get overrideHttpClient => false;
110+
}
111+
112+
void main() {
113+
//CustomBindings();
114+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
115+
testWidgets('Run simple query and check if we find all the element displayed',
116+
configureTestForSimpleQuery);
117+
testWidgets('Run simple subscription and wait the result of the update',
118+
configureTestForSimpleSubscriptionUpdate);
119+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Chat {
2+
final int id;
3+
final String name;
4+
final String description;
5+
6+
const Chat({required this.id, required this.name, required this.description});
7+
8+
factory Chat.fromJSON(Map<String, dynamic> json) {
9+
return Chat(
10+
id: json["id"] as int,
11+
name: json["name"].toString(),
12+
description: json["description"].toString());
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:graphql_flutter/graphql_flutter.dart';
3+
4+
void main({GraphQLClient? client, Widget? childToTest}) => runApp(
5+
MockAppView(client: ValueNotifier(client!), childToTest: childToTest!));
6+
7+
class MockAppView extends StatelessWidget {
8+
final ValueNotifier<GraphQLClient> client;
9+
final Widget childToTest;
10+
11+
MockAppView({required this.client, required this.childToTest});
12+
13+
@override
14+
Widget build(BuildContext context) {
15+
return GraphQLProvider(
16+
client: client,
17+
child: MaterialApp(
18+
title: 'Mock App',
19+
home: childToTest,
20+
),
21+
);
22+
}
23+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/// ListView flutter Widget implementation to display all the information
2+
/// of the chats that are created by by the client chat app.
3+
///
4+
/// author: https://github.com/vincenzopalazzo
5+
import 'package:flutter/material.dart';
6+
import 'package:graphql_flutter/graphql_flutter.dart';
7+
import 'package:logger/logger.dart';
8+
9+
import '../model/chat_app_model.dart';
10+
11+
class ListChatQueryView extends StatelessWidget {
12+
/// N.B: Please to not use this approach in a dev environment but
13+
/// consider to use code generator or put the query content somewhere else.
14+
String query = r"""
15+
query {
16+
getChats {
17+
__typename
18+
id
19+
description
20+
name
21+
}
22+
}
23+
""";
24+
Logger _logger = Logger();
25+
List<Chat> _chats = [];
26+
27+
@override
28+
Widget build(BuildContext context) {
29+
return Query(
30+
options: QueryOptions<List<Chat>>(
31+
fetchPolicy: FetchPolicy.networkOnly,
32+
parserFn: (Map<String, dynamic> json) {
33+
var rawList = List.of(json["getChats"] as List<dynamic>);
34+
return rawList
35+
.map((jsonChat) =>
36+
Chat.fromJSON(Map.from(jsonChat as Map<String, dynamic>)))
37+
.toList();
38+
},
39+
document: gql(query)),
40+
builder: (QueryResult result,
41+
{VoidCallback? refetch, FetchMore? fetchMore}) {
42+
if (result.hasException) {
43+
_logger.e(result.exception);
44+
throw Exception(result.exception);
45+
}
46+
if (result.isLoading) {
47+
_logger.i("Still loading");
48+
return const Text("Loading chats");
49+
}
50+
_logger.d(result.data ?? "Data is undefined");
51+
_chats = result.parsedData as List<Chat>;
52+
return ListView(
53+
children: _chats.map((chatData) => Text(chatData.name)).toList(),
54+
);
55+
});
56+
}
57+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/// ListView flutter Widget implementation to display all the information
2+
/// of the chats that are created by by the client chat app.
3+
///
4+
/// author: https://github.com/vincenzopalazzo
5+
import 'package:flutter/material.dart';
6+
import 'package:graphql_flutter/graphql_flutter.dart';
7+
import 'package:logger/logger.dart';
8+
9+
import '../model/chat_app_model.dart';
10+
11+
class ListChatSubscriptionView extends StatelessWidget {
12+
/// N.B: Please to not use this approach in a dev environment but
13+
/// consider to use code generator or put the query content somewhere else.
14+
String query = r"""
15+
subscription {
16+
chatCreated {
17+
id
18+
name
19+
description
20+
}
21+
}
22+
""";
23+
Logger _logger = Logger();
24+
List<Chat> _chats = [];
25+
26+
@override
27+
Widget build(BuildContext context) {
28+
return Subscription(
29+
options: SubscriptionOptions(
30+
parserFn: (Map<String, dynamic> json) {
31+
return Chat.fromJSON(json["chatCreated"] as Map<String, dynamic>);
32+
},
33+
document: gql(query),
34+
),
35+
builder: (result) {
36+
if (result.hasException) {
37+
_logger.e(result.exception);
38+
return Text(result.exception.toString());
39+
}
40+
41+
if (result.isLoading) {
42+
return const Center(
43+
child: CircularProgressIndicator(),
44+
);
45+
}
46+
// ResultAccumulator is a provided helper widget for collating subscription results.
47+
_logger.d(result.data ?? "Data is undefined");
48+
var chat = result.parsedData as Chat;
49+
return ResultAccumulator.appendUniqueEntries(
50+
latest: _chats,
51+
builder: (context, {results}) =>
52+
ListView(children: [Text(chat.name)]),
53+
);
54+
});
55+
}
56+
}

0 commit comments

Comments
 (0)