DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 966,904 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Flutter: Up your testing game
Reme Le Hane
Reme Le Hane

Posted on • Updated on • Originally published at remelehane.dev

Flutter: Up your testing game

Today we going to look at a great utility provided by Flutter's testing framework which gives us a lot more power when it comes to accurately test our widgets.

Very often widgets can very simply be tested using find.byType, find.text and find.byKey. Each of these is quite simple to use, and which you choose will depend on what exactly you are trying to test for, however, there are some scenarios where the basic tests like this will not yield valuable results.

Take the following widget as an example:

class SampleWidget extends StatelessWidget {
  final bool complete;

  const SampleWidget({required this.complete, Key? key}) : super(key: key);

  @override
  Widget build(context) {
    return complete
        ? Icon(Icons.check_circle, color: AppTheme.strongBlue)
        : Icon(Icons.circle_outlined, color: AppTheme.strongBlue);
  }
}
Enter fullscreen mode Exit fullscreen mode

Simple Usecase (Icon)

Personally, I do not usually test all my widgets, the above would be a sample of a very simple use-case that would make me consider writing the test, while it is extremely basic, this widget itself does pose some logic, there is a decision being made within this widget and while it's nothing complicated it serves the purpose of illustrating an ideal scenario for the test.

In the above widget, there is no text I can look for, I have not supplied any Keys for the individual icons and they are both icons, so using their type would not yield in an accurate test.

If I were to write the test like:

    testWidgets('Should render the check_circile_icon', (tester) async {
      await tester.pumpApp(const SampleWidget(complete: true));

      await tester.pumpAndSettle();

      final iconFinder = find.byType(Icon);

      expect(iconFinder, findsOneWidget);
    });

    testWidgets('Should render the circle_outlined icon', (tester) async {
      await tester.pumpApp(const SampleWidget(complete: false));

      await tester.pumpAndSettle();

      final iconFinder = find.byType(Icon);

      expect(iconFinder, findsOneWidget);
    });
Enter fullscreen mode Exit fullscreen mode

They would both certainly pass, and if one were to look at the coverage report, that too would indicate 100% test coverage, but the test as a whole is pretty worthless, while it is running the logic, the logic is certainly working, your test in no way proves this.

If you are going to take the time to write the test (and I hope you do), the test should always provide value beyond that of the coverage report, testing for line coverage dilutes the value and purpose of unit testing your code.

This is where find.byWidgetPredicate comes in handy and will allow you to write the same test above while being able to uniquely identify the individual icons.

find.byWidgetPredicate is a function based lookup that provides the widget as its function argument, this allows you to use attributes of the widget to specifically target unique instances of the same widget.

If we look at the next example, I have updated the iconFinder to make use of find.byWidgetPredicate lookup instead of the find.byType:

    testWidgets('Should render the circle_outlined icon', (tester) async {
      await tester.pumpApp(const SampleWidget(complete: false));

      await tester.pumpAndSettle();

      final iconFinder = find.byWidgetPredicate(
        (widget) => widget is Icon && widget.icon == Icons.circle_outlined,
      );

      expect(textFinder, findsOneWidget);
    });
Enter fullscreen mode Exit fullscreen mode

As you can see, within the function body we are looking for a widget that is an Icon(so a type comparison) and that the icon property of that Icon widget matches the IconData Icons.circle_outlined.

That way if for some reason, someone went and changed the false icon to Icons.menu for some strange reason, the find.byWidgetPredicate lookup would fail. If we had used the find.byTypeor even find.byKey, assuming we had provided unique keys, the test would have continued to pass.

The find.byWidgetPredicate lookup within widget testing allows you to write near bulletproof tests.

Better usecase (RichText)

Above was a very simple example, but within Flutter, if one wants to write a single line of text, but have a single word a phrase styled differently, be it bold or italic, we have to use the RichText widget along with a sequence of TextSpan's in order to achieve the desired result.

Take this example:

        RichText(
        text: TextSpan(
          children: [
            const TextSpan(
              text: "Required",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: Colors.black,
              ),
            ),
            TextSpan(
              text: " 70%",
              style: const TextStyle(
                color: Colors.black,
              ),
            ),
          ],
        ),
      )
Enter fullscreen mode Exit fullscreen mode

While this specific widget I probably would not actually test, it's another great example for using the find.byWidgetPredicate in a more complicated scenario.

    final requiredScoreFinder = find.byWidgetPredicate(
      (widget) =>
          widget is RichText &&
          widget.text.toPlainText().contains("70%"),
    );
Enter fullscreen mode Exit fullscreen mode

for the above widget, you would target it something like the above sample, as the RichText widget actually does break the text up into multiple parts, find.text will not work.

You can use contains to do a partial lookup or you can simply use strict equality, contains may be simpler for longer sentences.

    final requiredScoreFinder = find.byWidgetPredicate(
      (widget) =>
          widget is RichText &&
          widget.text.toPlainText() == "Results 70%",
    );
Enter fullscreen mode Exit fullscreen mode

As you can hopefully now see, find.byWidgetPredicate can be a very powerful tool in your testing tool belt and will allow you to write even better, more accurate tests.


I hope you found this interesting, and if you have any questions, comments, or improvements, feel free to drop a comment. Enjoy your Flutter development journey :D

If you enjoyed it, a like would be awesome, and if you really liked it, a cup of coffee would be great.

Thanks for reading.


Wish to carry on with the topic of Unit Testing, take a look at:

Top comments (0)

Update Your DEV Experience Level:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›