Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to `setUp` a `WidgetTester` for Multiple Tests in Flutter

1. The Problem

The testWidgets function is apparently only a subcase of the test function.

A use case I'm trying to solve right now is to pump the same widget for multiple testWidgets, a setUp for multiple testWidgets. However, how can I do this if it creates a new instance inside each test?

I've tried to initialize a WidgetTester outside the tests, in the main(), but WidgetTester has only a private constructor:

class WidgetTester 
  extends WidgetController 
    implements HitTestDispatcher, TickerProvider {
  WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
    if (binding is LiveTestWidgetsFlutterBinding)
      binding.deviceEventDispatcher = this;
}

I don't quite get how the Flutter team made this work, but initializing a WidgetTester in the same way they did inside the testWidgets function isn't working for me:

final TestWidgetsFlutterBinding binding 
  = TestWidgetsFlutterBinding.ensureInitialized() 
    as TestWidgetsFlutterBinding;
final WidgetTester tester = WidgetTester._(binding);

2. An Example

A simple example would be to try to break down the tests of the Flutter demo that is created with each new Flutter project from flutter create. In it, we could try to separate the initial setup test of the app from the tapping action test:

testWidgets('Initial setup', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  expect(find.text('0'), findsOneWidget);
  expect(find.text('1'), findsNothing);
});

testWidgets('Increment the counter on tap', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();

  expect(find.text('0'), findsNothing);
  expect(find.text('1'), findsOneWidget);
});

The idea would be to try to move the await tester.pumpWidget(MyApp()); into a setUp function.

like image 594
Philippe Fanaro Avatar asked Feb 22 '20 19:02

Philippe Fanaro


People also ask

How do you test a stateful widget in Flutter?

For that, we will use a widget tester to tap the submit button and Mockito's verify method. The snippet is the same for both create and update tests. final saveButton = find. byKey(const Key('submit-button')); expect(saveButton, findsOneWidget); await tester.


1 Answers

Below is what looks like the current way to solve for this in Flutter.

To summarize:

  1. Create the group(..) structure inside main()
  2. Create your own private methods from inside that structure, for each group of testing you want. For each of these private methods:
    • Pass in the WidgetTester instance
    • Let them be async
  3. And then you should only have a single call to testWidgets(..)
    • Inside this method, is where you call the private methods you set up to distribute test logic
    • Call each of these with await, so they don't run concurrently

So far I didn't find a way for the output to indicate each "sub-test" it ran, so just using print(...) statements for now.

This is a demo for some QR Code logic:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:qr_code_demo/app/appRoutes.dart';
import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';

class MockNavigatorObserver extends Mock implements NavigatorObserver {}

void main() {
  group('MainPage navigation tests', () {
    NavigatorObserver mockObserver;

    _loadAppHomeScreen(WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          routes: AppRoutes.getRouteMap(),
          home: AppHomeScreen(),
          navigatorObservers: [mockObserver],
        ),
      );
    }

    setUp(() {
      mockObserver = MockNavigatorObserver();
    });

    Future<Null> _verifyLayoutElements(WidgetTester tester) async {
      print('_verifyLayoutElements');
      expect(find.byIcon(Icons.scanner), findsOneWidget);
      expect(find.byType(FloatingActionButton), findsOneWidget);
      expect(find.byType(RaisedButton), findsOneWidget);
    }

    Future<Null> _navigateToQrScannerScreen(WidgetTester tester) async {
      print('_navigateToQrScannerScreen');

      await tester.tap(find.byIcon(Icons.scanner));
      await tester.pumpAndSettle();

      verify(mockObserver.didPush(any, any));

      expect(find.byType(AppHomeScreen), findsNothing);
      expect(find.byType(QrScannerScreen), findsOneWidget);
    }

    testWidgets('AppHomeScreen WidgetTester', (WidgetTester tester) async {
      await _loadAppHomeScreen(tester);

      await _verifyLayoutElements(tester);
      await _navigateToQrScannerScreen(tester);
    });
  });
}

Thanks to: https://iiro.dev/2018/08/22/writing-widget-tests-for-navigation-events/

  • Scroll to code for this file: test/navigation_test.dart

====

And double-thanks, because the navigation testing logic including with this example is thanks to @iiro's post: https://stackoverflow.com/a/51983194/2162226

Here is the appRoutes.dart file:

import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';  

class AppRoutes {

  static const String AppHome = 'AppHome';
  static const String QrScanner = 'QrScanner';

  static String initialRoute() {
    return AppHome;
  }
  
  static getRouteMap() {
    
    return {
      AppRoutes.AppHome: (context) => AppHomeScreen(),
      AppRoutes.QrScanner: (context) => QrScannerScreen()
    };
    
  }
}
like image 51
Gene Bo Avatar answered Sep 23 '22 07:09

Gene Bo