Skip to content

Commit

Permalink
graphql_flutter: add integration testing
Browse files Browse the repository at this point in the history
Signed-off-by: Vincenzo Palazzo <[email protected]>
  • Loading branch information
vincenzopalazzo committed Aug 21, 2022
1 parent 466f091 commit bd4491d
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 15 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/graphql_integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: "graphql_flutter: integration testing "
on: [push, pull_request]
jobs:
integration_tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
- run: flutter pub get
- run: chromedriver --port=4444 &
- name: graphql_chat intergration testing
run: |
cd packages/graphql_flutter/example/graphql_chat
flutter pub dev
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/intergration_test.dart -d chrome
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,6 @@ class SocketSubProtocol {
static const String graphqlTransportWs = GraphQLProtocol.graphqlTransportWs;
}

/// graphql-ws: The new (not to be confused with the graphql-ws library).
/// NB. This protocol is it no longer maintained, please consider
/// to use `SocketSubProtocol.graphqlTransportWs`.
static const String graphqlWs = GraphQLProtocol.graphqlWs;

/// graphql-transport-ws: New ws protocol used by most Apollo Server instances
/// with subscriptions enabled use this library.
/// N.B: not to be confused with the graphql-ws library that implement the
/// old ws protocol.
static const String graphqlTransportWs = GraphQLProtocol.graphqlTransportWs;
}

/// ALL protocol supported by the library
class GraphQLProtocol {
GraphQLProtocol._();
Expand Down
5 changes: 3 additions & 2 deletions packages/graphql_flutter/example/graphql_chat/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
CC=flutter
FMT=format
PATH=
default: get fmt check fmt

default: get fmt check

get:
$(CC) pub get
Expand All @@ -23,4 +24,4 @@ run:
$(CC) run -d linux

dep_upgrade:
$(CC) pub upgrade --major-versions
$(CC) pub upgrade --major-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/// Client Provider is a utils class that contains in a single place
/// all the client used to ran the integration testing with all the
/// servers.
///
/// The test supported in this class for the moment are:
/// - GraphQl Client Chat: https://github.com/vincenzopalazzo/keep-safe-graphql
///
/// author: https://github.com/vincenzopalazzo
/// ClientProvider is a singleton implementation that contains
/// all the client supported to implement the integration testing.
import 'package:graphql_flutter/graphql_flutter.dart';

class ClientProvider {
static ClientProvider? _instance;

GraphQLClient? _chatApp;

ClientProvider._internal();

factory ClientProvider() {
_instance ??= ClientProvider._internal();
return _instance!;
}

/// Init the client regarding the chat app server
/// crete only a single instance of it.
GraphQLClient chatAppClient(
{GraphQLCache? cache, bool forceRecreation = false}) {
if (_chatApp == null || forceRecreation) {
final HttpLink httpLink = HttpLink(
'https://api.chat.graphql-flutter.dev/graphql',
defaultHeaders: {
"Accept": "application/json",
"Access-Control_Allow_Origin": "*",
},
);
var wsLink = WebSocketLink(
'ws://api.chat.graphql-flutter.dev/graphql',
config: const SocketClientConfig(
inactivityTimeout: Duration(seconds: 40),
),
subProtocol: GraphQLProtocol.graphqlTransportWs,
);
final Link link = httpLink.split(
(request) => request.isSubscription,
wsLink,
httpLink,
);
if (!forceRecreation) {
_chatApp = GraphQLClient(link: link, cache: cache ?? GraphQLCache());
} else {
return GraphQLClient(link: link, cache: cache ?? GraphQLCache());
}
}
return _chatApp!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/// Integration test regarding the graphq_flutter widget
///
/// In this particular test suite we implement a battery of test
/// to make sure that all the operation on a widget are done
/// correctly.
///
/// More precise in this file all the test are regarding the web socket and
/// the subscription.
///
/// Trying to reproduce the problems caused by the following issue
/// - Subscription not receive update
/// - https://github.com/zino-hofmann/graphql-flutter/issues/1163
/// - https://github.com/zino-hofmann/graphql-flutter/issues/1162
///
/// author: https://github.com/vincenzopalazzo
import 'package:flutter_test/flutter_test.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:integration_test/integration_test.dart';
import 'package:logger/logger.dart';
import 'clients/ClientProvider.dart';
import 'model/chat_app_model.dart';
import 'widget/MockAppView.dart' as app;
import 'widget/list_view_query.dart';
import 'widget/list_view_subscription.dart';

/// Configure the test to check if the ws `GraphQLProtocol.graphqlTransportWs` receive
/// update correctly.
Future<void> configureTestForSimpleSubscriptionUpdate(dynamic tester) async {
var logger = Logger();

/// build a simple client with a in memory
var client = ClientProvider().chatAppClient();
app.main(client: client, childToTest: ListChatSubscriptionView());
await tester.pumpAndSettle();
var listChats = await client.query(QueryOptions(document: gql(r"""
query {
getChats {
__typename
id
description
name
}
}
""")));
logger.d("Exception: ${listChats.exception}");
expect(listChats.hasException, isFalse);
logger.i("Chats contains inside the server: ${listChats.data}");
var chatName = "graphql_flutter test chat ${DateTime.now()}";
var description = "graphql_flutter integration test in ${DateTime.now()}";
await client.mutate(MutationOptions(document: gql(r"""
mutation CreateChat($description: String!, $name: String!){
createChat(description: $description, name: $name) {
__typename
name
}
}
"""), variables: {
"name": chatName,
"description": description,
}));

//find new item in the list
await tester.pump(const Duration(seconds: 10));
final subscriptionItem = find.text(chatName);
expect(subscriptionItem, findsOneWidget);
}

/// Configure the test to check if we receive all the element from the graphql
Future<void> configureTestForSimpleQuery(dynamic tester) async {
var logger = Logger();

/// build a simple client with a in memory
var client = ClientProvider().chatAppClient();
app.main(client: client, childToTest: ListChatQueryView());
await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 10));
var listChats = await client.query(QueryOptions(
document: gql(r"""
query {
getChats {
__typename
id
description
name
}
}
"""),
parserFn: (Map<String, dynamic> json) {
var rawList = List.of(json["getChats"] as List<dynamic>);
return rawList
.map((jsonChat) => Chat.fromJSON(jsonChat as Map<String, dynamic>))
.toList();
}));
logger.d("Exception: ${listChats.exception}");
expect(listChats.hasException, isFalse);
logger.i("Chats contains inside the server: ${listChats.data}");
var chats = listChats.parsedData as List<Chat>;
for (var chat in chats) {
//find new item in the list
final subscriptionItem = find.text(chat.name);

/// Finds not only one but more than one
expect(subscriptionItem, findsWidgets);
}
}

class CustomBindings extends AutomatedTestWidgetsFlutterBinding {
@override
bool get overrideHttpClient => false;
}

void main() {
//CustomBindings();
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Run simple query and check if we find all the element displayed',
configureTestForSimpleQuery);
testWidgets('Run simple subscription and wait the result of the update',
configureTestForSimpleSubscriptionUpdate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Chat {
final int id;
final String name;
final String description;

const Chat({required this.id, required this.name, required this.description});

factory Chat.fromJSON(Map<String, dynamic> json) {
return Chat(
id: json["id"] as int,
name: json["name"].toString(),
description: json["description"].toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

void main({GraphQLClient? client, Widget? childToTest}) => runApp(
MockAppView(client: ValueNotifier(client!), childToTest: childToTest!));

class MockAppView extends StatelessWidget {
final ValueNotifier<GraphQLClient> client;
final Widget childToTest;

MockAppView({required this.client, required this.childToTest});

@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: client,
child: MaterialApp(
title: 'Mock App',
home: childToTest,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/// ListView flutter Widget implementation to display all the information
/// of the chats that are created by by the client chat app.
///
/// author: https://github.com/vincenzopalazzo
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:logger/logger.dart';

import '../model/chat_app_model.dart';

class ListChatQueryView extends StatelessWidget {
/// N.B: Please to not use this approach in a dev environment but
/// consider to use code generator or put the query content somewhere else.
String query = r"""
query {
getChats {
__typename
id
description
name
}
}
""";
Logger _logger = Logger();
List<Chat> _chats = [];

@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions<List<Chat>>(
fetchPolicy: FetchPolicy.networkOnly,
parserFn: (Map<String, dynamic> json) {
var rawList = List.of(json["getChats"] as List<dynamic>);
return rawList
.map((jsonChat) =>
Chat.fromJSON(Map.from(jsonChat as Map<String, dynamic>)))
.toList();
},
document: gql(query)),
builder: (QueryResult result,
{VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
_logger.e(result.exception);
throw Exception(result.exception);
}
if (result.isLoading) {
_logger.i("Still loading");
return const Text("Loading chats");
}
_logger.d(result.data ?? "Data is undefined");
_chats = result.parsedData as List<Chat>;
return ListView(
children: _chats.map((chatData) => Text(chatData.name)).toList(),
);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/// ListView flutter Widget implementation to display all the information
/// of the chats that are created by by the client chat app.
///
/// author: https://github.com/vincenzopalazzo
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:logger/logger.dart';

import '../model/chat_app_model.dart';

class ListChatSubscriptionView extends StatelessWidget {
/// N.B: Please to not use this approach in a dev environment but
/// consider to use code generator or put the query content somewhere else.
String query = r"""
subscription {
chatCreated {
id
name
description
}
}
""";
Logger _logger = Logger();
List<Chat> _chats = [];

@override
Widget build(BuildContext context) {
return Subscription(
options: SubscriptionOptions(
parserFn: (Map<String, dynamic> json) {
return Chat.fromJSON(json["chatCreated"] as Map<String, dynamic>);
},
document: gql(query),
),
builder: (result) {
if (result.hasException) {
_logger.e(result.exception);
return Text(result.exception.toString());
}

if (result.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
// ResultAccumulator is a provided helper widget for collating subscription results.
_logger.d(result.data ?? "Data is undefined");
var chat = result.parsedData as Chat;
return ResultAccumulator.appendUniqueEntries(
latest: _chats,
builder: (context, {results}) =>
ListView(children: [Text(chat.name)]),
);
});
}
}
Loading

0 comments on commit bd4491d

Please sign in to comment.