flutterdartgraphqlflutter-testflutter-graphql

Flutter Test Mock GraphQL Mutation result


I'm trying to create widget tests for a flutter application using GraphQL. What I want to do is to test the behaviour of the app which depends on the result of a GraphQL Mutation on a user action.

This is a very simple example of the app:

class FirstScreen extends StatelessWidget {
  @override
  Widget return Container(
    child: Mutation(
      options: myMutationOptions,
      onCompleted: (dynamic result) {
        final bool myBool = result['bool'] as bool;
        if (myBool) {
          Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondScreen()));
        } else {
          Navigator.of(context).push(MaterialPageRoute(builder: (context) => ThirdScreen()));
        }
      },
      builder: (RunMutation runMutation, QueryResult queryResult) {
       return FlatButton(
         child: Text('Button'),
         onPressed: () async {
           await runMutation(myParameters).networkResult;
         },
       );
      },
    ),
  );
}

What I would like to do is to mock the result of the mutation so in my widget tests, I can test that the button redirects to the SecondScreen or ThirdScreen depending of the result myBool.

How can I do that ?


Solution

  • I finally managed to successfully mock a GraphQL Mutation. Here is how I did it, it is inspired from @Gpack's comment but I had to add some modifications and details to it.

    To make it easy to use I created a wrapper widget GraphQLMutationMocker :

    class MockClient extends Mock implements Client {
      MockClient({
        this.mockedResult,
        this.mockedStatus = 200,
      });
      final Map<String, dynamic> mockedResult;
      final int mockedStatus;
    
      @override
      Future<StreamedResponse> send(BaseRequest request) {
        return Future<StreamedResponse>.value(
          StreamedResponse(
            Stream.value(utf8.encode(jsonEncode(mockedResult))),
            mockedStatus,
          ),
        );
      }
    }
    
    class GraphQLMutationMocker extends StatelessWidget {
      const GraphQLMutationMocker({
        @required this.child,
        this.mockedResult = const {},
        this.mockedStatus = 200,
        this.url = 'http://url',
        this.storagePrefix = 'test',
      });
      final Widget child;
    
      final Map<String, dynamic> mockedResult;
    
      final int mockedStatus;
    
      final String url;
    
      final String storagePrefix;
    
      @override
      Widget build(BuildContext context) {
        final mockClient = MockClient(
          mockedResult: mockedResult,
          mockedStatus: mockedStatus,
        );
        final httpLink = HttpLink(
          uri: url,
          httpClient: mockClient,
        );
        final graphQLClient = ValueNotifier(
          GraphQLClient(
            cache: InMemoryCache(storagePrefix: storagePrefix),
            link: httpLink,
          ),
        );
        return GraphQLProvider(
          client: graphQLClient,
          child: child,
        );
      }
    }
    

    Then it was pretty easy to write the tests

    group('Test mutation', () {
      
      testWidgets('It should redirect to SecondScreen', (WidgetTester tester) async {
    
        await tester.pumpWidget(GraphQLMutationMocker(
          mockedResult: <String, dynamic>{
            'data': {
              'bool': true,
            },
          },
          child: FirstScreen(),
        ));
        // Click on button
        await tester.tap(find.text('Button'));
        await tester.pumpAndSettle();
    
        // Check I'm on the right screen
        expect(find.byType(SecondScreen), findsOneWidget);
        expect(find.byType(ThirdScreen), findsNothing);
      });
    
      testWidgets('It should redirect to ThirdScreen', (WidgetTester tester) async {
    
        await tester.pumpWidget(GraphQLMutationMocker(
          mockedResult: <String, dynamic>{
            'data': {
              'bool': false,
            },
          },
          child: FirstScreen(),
        ));
        // Click on button
        await tester.tap(find.text('Button'));
        await tester.pumpAndSettle();
        
        // Check I'm on the right screen
        expect(find.byType(SecondScreen), findsNothing);
        expect(find.byType(ThirdScreen), findsOneWidget);
      });
    })