Skip to content

Commit

Permalink
Merge pull request #1 from lesliearkorful/leslie/mock
Browse files Browse the repository at this point in the history
Use Equatable/EquatableMixin in classes to help in mock testing
  • Loading branch information
lesliearkorful authored Dec 10, 2020
2 parents f85c45e + bc0193e commit 02617d7
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 4 deletions.
46 changes: 43 additions & 3 deletions packages/graphql/lib/src/core/query_options.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:gql/ast.dart';
import 'package:gql/language.dart';
import 'package:graphql/client.dart';
Expand Down Expand Up @@ -105,7 +106,7 @@ class BaseOptions extends RawOperationData {
}

/// Query options.
class QueryOptions extends BaseOptions {
class QueryOptions extends BaseOptions with EquatableMixin {
QueryOptions({
@Deprecated('The "document" option has been deprecated, use "documentNode" instead')
String document,
Expand All @@ -129,14 +130,26 @@ class QueryOptions extends BaseOptions {
/// The time interval (in milliseconds) on which this query should be
/// re-fetched from the server.
int pollInterval;

@override
List<Object> get props => [
documentNode?.definitions,
documentNode?.span?.sourceUrl,
variables,
fetchPolicy,
errorPolicy,
optimisticResult,
pollInterval,
context,
];
}

typedef OnMutationCompleted = void Function(dynamic data);
typedef OnMutationUpdate = void Function(Cache cache, QueryResult result);
typedef OnError = void Function(OperationException error);

/// Mutation options
class MutationOptions extends BaseOptions {
class MutationOptions extends BaseOptions with EquatableMixin {
MutationOptions({
@Deprecated('The "document" option has been deprecated, use "documentNode" instead')
String document,
Expand All @@ -160,6 +173,20 @@ class MutationOptions extends BaseOptions {
OnMutationCompleted onCompleted;
OnMutationUpdate update;
OnError onError;

@override
List<Object> get props => [
documentNode?.definitions,
documentNode?.span?.sourceUrl,
variables,
fetchPolicy,
errorPolicy,
optimisticResult,
context,
onCompleted,
update,
onError,
];
}

class MutationCallbacks {
Expand Down Expand Up @@ -250,7 +277,7 @@ class MutationCallbacks {
}

// ObservableQuery options
class WatchQueryOptions extends QueryOptions {
class WatchQueryOptions extends QueryOptions with EquatableMixin {
WatchQueryOptions({
@Deprecated('The "document" option has been deprecated, use "documentNode" instead')
String document,
Expand Down Expand Up @@ -281,6 +308,19 @@ class WatchQueryOptions extends QueryOptions {
bool fetchResults;
bool eagerlyFetchResults;

@override
List<Object> get props => [
documentNode?.definitions,
documentNode?.span?.sourceUrl,
variables,
fetchPolicy,
errorPolicy,
optimisticResult,
context,
fetchResults,
eagerlyFetchResults,
];

/// Checks if the [WatchQueryOptions] in this class are equal to some given options.
bool areEqualTo(WatchQueryOptions otherOptions) {
return !_areDifferentOptions(this, otherOptions);
Expand Down
19 changes: 18 additions & 1 deletion packages/graphql/lib/src/core/query_result.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:async' show FutureOr;

import 'package:equatable/equatable.dart';
import 'package:graphql/src/exceptions/exceptions.dart';
import 'package:meta/meta.dart';

/// The source of the result data contained
///
Expand All @@ -23,7 +25,8 @@ final eagerSources = {
QueryResultSource.OptimisticResult
};

class QueryResult {
// ignore: must_be_immutable
class QueryResult extends Equatable {
QueryResult({
this.data,
this.exception,
Expand Down Expand Up @@ -60,6 +63,20 @@ class QueryResult {

/// Whether the response includes an exception
bool get hasException => (exception != null);

@override
List<Object> get props => [
data,
hasException,
optimistic,
exception?.clientException,
exception?.graphqlErrors,
exception?.addError,
timestamp,
loading,
optimistic,
source?.index
];
}

class MultiSourceResult {
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies:
rxdart: ^0.24.0
websocket: ^0.0.5
quiver: '>=2.0.0 <3.0.0'
equatable: ^1.2.5
dev_dependencies:
pedantic: ^1.8.0+1
mockito: ^4.0.0
Expand Down
177 changes: 177 additions & 0 deletions packages/graphql/test/graphql_client_mock_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'package:gql/ast.dart';
import 'package:graphql/src/core/query_options.dart';
import 'package:graphql/src/core/query_result.dart';
import 'package:graphql/src/graphql_client.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

main() {
group('GraphQLClient Mock Test |', () {
GraphQLClient client;

setUpAll(() {
client = _MockClient();
});

test('Check for equal MutationOptions', () {
final email = "[email protected]";
final password = "password";
final a = MutationOptions(
documentNode: document,
variables: {'email': email, 'password': password},
);
final b = MutationOptions(
documentNode: document,
variables: {'email': email, 'password': password},
);
expect(a, b);
});

test('Login Mutation', () async {
final email = "[email protected]";
final password = "password";
final options = MutationOptions(
documentNode: document,
variables: {'email': email, 'password': password},
);
when(client.mutate(options))
.thenAnswer((_) => Future.value(loginResponse));
final res = await client.mutate(
MutationOptions(
documentNode: document,
variables: {'email': email, 'password': password},
),
);
expect(res, loginResponse);
});
});
}

class _MockClient extends Mock implements GraphQLClient {}

final loginResponse = QueryResult(
data: {
"login": {
"errors": null,
"expiresAt": 1595243976060,
"status": 200,
"token":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTUyNDM5NzYsInN1YiI6MX0.D4x4I2I_gvJJFh-Endbx2iamiyJjQqyeUbIZS1riF5E",
"user": {
"email": "[email protected]",
"firstName": "Leslie",
"id": 1,
"lastName": "User",
"name": "Test User",
}
}
},
);

const loginAST = OperationDefinitionNode(
type: OperationType.mutation,
name: NameNode(value: 'login'),
variableDefinitions: [
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'email')),
type: NamedTypeNode(name: NameNode(value: 'String'), isNonNull: true),
defaultValue: DefaultValueNode(value: null),
directives: []),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'password')),
type: NamedTypeNode(name: NameNode(value: 'String'), isNonNull: true),
defaultValue: DefaultValueNode(value: null),
directives: [])
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'login'),
alias: null,
arguments: [
ArgumentNode(
name: NameNode(value: 'email'),
value: VariableNode(name: NameNode(value: 'email'))),
ArgumentNode(
name: NameNode(value: 'password'),
value: VariableNode(name: NameNode(value: 'password')))
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'errors'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'message'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'property'),
alias: null,
arguments: [],
directives: [],
selectionSet: null)
])),
FieldNode(
name: NameNode(value: 'expiresAt'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'status'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'token'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'user'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'email'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'firstName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'id'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'lastName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'name'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
]))
]))
]));
const document = DocumentNode(definitions: [loginAST]);

0 comments on commit 02617d7

Please sign in to comment.