An in-depth analysis of testing widgets in Flutter. Part II. Finder and WidgetTester Classes

The translation of the material was prepared as part of the online course " Flutter Mobile Developer ".



We also invite everyone to a free two-day intensive "Creating a Flutter App for Web, iOS and Android" . On the intensive course, we will learn exactly how Flutter allows you to create applications for the Web platform, and why it is now stable functionality; how the web assembly works. Let's write an application with networking. Details and registration here .






This is a continuation of the first part of the article on testing widgets in Flutter .





Let's continue our exploration of the widget testing process. 





, testWidgets()



. , , , β€” . , , , .





:

  1. .





  2. test.





  3. testWidgets()



    , .





.





?

:





  1. .





  2. .





, . :





  1. .





  2. - (, ).





  3. (, ), .





  4. , .





, , . test:





void main() {

  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here
    },
  );

}
      
      



, WidgetTester



, . .





, pumpWidget()



:





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
          ),
        ),
      );
    },
  );
      
      



( await



, .)





.





WidgetTester



, .





-

, «» , .





, , , β€” , . .





, ? -, Finder



. ( , .)





, -  β€” , , . .





:





find.byType()

Text:





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Text('Hi there!'),
            ),
          ),
        ),
      );

      var finder = find.byType(Text);
    },
  );
      
      



- CommonFinders



find



. byType()



. , , . , Text



, , :





find.text()

Text, find.text()



:





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Text('Hi there!'),
            ),
          ),
        ),
      );

      var finder = find.text('Hi there!');
    },
  );
      
      



EditableText



, TextField



.





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      var controller = TextEditingController.fromValue(TextEditingValue(text: 'Hi there!'));

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              child: TextField(controller: controller,),
            ),
          ),
        ),
      );

      var finder = find.text('Hi there!');
    },
  );
      
      



find.byKey()

 β€” :





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Icon(
                Icons.add,
                key: Key('demoKey'),
              ),
            ),
          ),
        ),
      );

      var finder = find.byKey(Key('demoKey'));
    },
  );
      
      



find.descendant() find.ancestor()

, , ( -).





, , Center, . :





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              key: Key('demoKey'),
              child: Icon(Icons.add),
            ),
          ),
        ),
      );
      
      var finder = find.descendant(
        of: find.byKey(Key('demoKey')),
        matching: find.byType(Icon),
      );
    },
  );
      
      



, Center ( of



) , -.





find.ancestor()



, , , , of



.





Center, :





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              key: Key('demoKey'),
              child: Icon(Icons.add),
            ),
          ),
        ),
      );

      var finder = find.ancestor(
        of: find.byType(Icon),
        matching: find.byKey(Key('demoKey')),
      );
    },
  );
      
      



-

find.xxxx()



Finder



. ?





, , -, , . BadlyWrittenWidgetFinder



.





  1.  MatchFinder



    .





class BadlyWrittenWidgetFinder extends MatchFinder {
  
  @override
  // TODO: implement description
  String get description => throw UnimplementedError();

  @override
  bool matches(Element candidate) {
    // TODO: implement matches
    throw UnimplementedError();
  }
  
}
      
      



2. matches()



, . , null



:





class BadlyWrittenWidgetFinder extends MatchFinder {

  BadlyWrittenWidgetFinder({bool skipOffstage = true})
      : super(skipOffstage: skipOffstage);

  @override
  String get description => 'Finds icons with no key';

  @override
  bool matches(Element candidate) {
    final Widget widget = candidate.widget;
    return widget is Icon && widget.key == null;
  }

}
      
      



3. , - CommonFinders



( find



):





extension BadlyWrittenWidget on CommonFinders {
  Finder byBadlyWrittenWidget({bool skipOffstage = true }) => BadlyWrittenWidgetFinder(skipOffstage: skipOffstage);
}
      
      



4. - , :





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(),
            body: Center(
              key: Key('demoKey'),
              child: Icon(Icons.add),
            ),
          ),
        ),
      );

      var finder = find.byBadlyWrittenWidget();
    },
  );
      
      



, -, WidgetTester



.





, WidgetTester

, , .





WidgetTester



. , , . :





setState()



, .





setState()



  , , . ? pump



.





pump

pump()



( ), pumpWidget()



, pumpAndSettle()



pump()



, ( ).





pumpWidget()

, pumpWidget()



. runApp()



, , pump()



. .





pump()

pump()



, . , :





class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  var count = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Text('$count'),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            setState(() {
              count++;
            });
          },
        ),
      ),
    );
  }
}
      
      



FloatingActionButton



, -.





: , , 1:





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here
      await tester.pumpWidget(CounterWidget());

      var finder = find.byIcon(Icons.add);
      await tester.tap(finder);
      
      // Ignore this line for now
      // It just verifies that the value is what we expect it to be
      expect(find.text('1'), findsOneWidget);
    },
  );
      
      



:





 





, Text



, , setState()



, . pump()



:





  testWidgets(
    'Test description',
    (WidgetTester tester) async {
      // Write your test here
      await tester.pumpWidget(CounterWidget());

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

      // Ignore this line for now
      // It just verifies that the value is what we expect it to be
      expect(find.text('1'), findsOneWidget);
    },
  );
      
      



:





 





, pump()



 β€” :





await tester.pump(Duration(seconds: 1));
      
      



, , .





pump



: . EnginePhase



:





enum EnginePhase {
  /// The build phase in the widgets library. See [BuildOwner.buildScope].
  build,

  /// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
  layout,

  /// The compositing bits update phase in the rendering library. See
  /// [PipelineOwner.flushCompositingBits].
  compositingBits,

  /// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
  paint,

  /// The compositing phase in the rendering library. See
  /// [RenderView.compositeFrame]. This is the phase in which data is sent to
  /// the GPU. If semantics are not enabled, then this is the last phase.
  composite,

  /// The semantics building phase in the rendering library. See
  /// [PipelineOwner.flushSemantics].
  flushSemantics,

  /// The final phase in the rendering library, wherein semantics information is
  /// sent to the embedder. See [SemanticsOwner.sendSemanticsUpdate].
  sendSemanticsUpdate,
}

await tester.pump(Duration.zero, EnginePhase.paint);
      
      



. , . .





pumpAndSettle()

pumpAndSettle()



 β€” , , pump, , . .





( ),  β€” -, .





await tester.pumpAndSettle(
        Duration(milliseconds: 10),
        EnginePhase.paint,
        Duration(minutes: 1),
      );
      
      



WidgetTester



Β« + Β». :





tester.drag()



, - . , X Y:





      var finder = find.byIcon(Icons.add);
      var moveBy = Offset(100, 100);
      var slopeX = 1.0;
      var slopeY = 1.0;

      await tester.drag(finder, moveBy, touchSlopX: slopeX, touchSlopY: slopeY);
      
      



,  tester.timedDrag()



:





      var finder = find.byIcon(Icons.add);
      var moveBy = Offset(100, 100);
      var dragDuration = Duration(seconds: 1);

      await tester.timedDrag(finder, moveBy, dragDuration);
      
      



, -, tester.dragFrom()



, .





      var dragFrom = Offset(250, 300);
      var moveBy = Offset(100, 100);
      var slopeX = 1.0;
      var slopeY = 1.0;

      await tester.dragFrom(dragFrom, moveBy, touchSlopX: slopeX, touchSlopY: slopeY);
      
      



   β€” tester.timedDragFrom()



.





      var dragFrom = Offset(250, 300);
      var moveBy = Offset(100, 100);
      var duration = Duration(seconds: 1);

      await tester.timedDragFrom(dragFrom, moveBy, duration);
      
      



. ,  tester.fling()



 tester.drag()



.





: «» .





:





      var dragFrom = Offset(250, 300);

      var gesture = await tester.startGesture(dragFrom);
      
      



, .





:





      var dragFrom = Offset(250, 300);

      var gesture = await tester.startGesture(dragFrom);
      
      await gesture.moveBy(Offset(50.0, 0));
      await gesture.moveBy(Offset(0.0, -50.0));
      await gesture.moveBy(Offset(-50.0, 0));
      await gesture.moveBy(Offset(0.0, 50.0));
      
      await gesture.up();
      
      



, , . , , , , , ( ,  β€” , - , ).





Finder



WidgetTester



.  β€” .






"Flutter Mobile Developer".





Β« Flutter Web, iOS AndroidΒ».








All Articles