DEV Community

loading...

Test behavior of widgets in Flutter

Arthur Denner
Software Engineer using JavaScript. Learning Flutter, Node, MongoDB and GraphQL. Addicted to TV shows, especially nordic. From πŸ‡§πŸ‡·, living in πŸ‡ΈπŸ‡ͺ.
・Updated on ・2 min read

Our widget tests should give us confidence about how they respond to user interactions - its behavior.

Using the CustomButton widget from a previous post, we can test if it fires its onPressed callback when tapped on all platforms.

Testing the behavior

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'path/to/CustomButton.dart';

void main() {
  Widget buildApp({VoidCallback onPressed}) {
    return MaterialApp(
      home: CustomButton(
        onPressed: onPressed,
      ),
    );
  }

  group('CustomButton >', () {
    group('behavior >', () {
      testWidgets(
        'calls onPressed when tapped on iOS',
        (tester) async {
          debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

          final log = <int>[];
          final onPressed = () => log.add(0);

          await tester.pumpWidget(buildApp(onPressed: onPressed));
          await tester.tap(find.byType(CustomButton));
          expect(log.length, 1);
          await tester.tap(find.byType(CustomButton));
          await tester.tap(find.byType(CustomButton));
          expect(log.length, 3);

          debugDefaultTargetPlatformOverride = null; <-- this is required
        },
      );

      testWidgets(
        'calls onPressed when tapped on other platforms',
        (tester) async {
          final log = <int>[];
          final onPressed = () => log.add(0);

          await tester.pumpWidget(buildApp(onPressed: onPressed));
          await tester.tap(find.byType(CustomButton));
          expect(log.length, 1);
          await tester.tap(find.byType(CustomButton));
          await tester.tap(find.byType(CustomButton));
          expect(log.length, 3);
        },
      );
    });
  });
}

Enter fullscreen mode Exit fullscreen mode

Using the same approach to override the platform, we can easily verify that our widget behaves properly on all platforms.

Now, if we change our implementation, our tests will assert if the behavior was maintained.

We can change from a CupertinoButton to a Container + GestureDetector on iOS and our tests will continue to pass because it's the same behavior:

Widget buildCupertinoWidget(BuildContext context) {
  return GestureDetector(
    onTap: onPressed,
    child: Container(
      child: Text('Click me'),
    ),
  );
}
Enter fullscreen mode Exit fullscreen mode

But if we remove the GestureDetector, our tests will fail because the widget doesn't behave as before:

Widget buildCupertinoWidget(BuildContext context) {
  return Container(
    child: Text('Click me'),
  );
}
Enter fullscreen mode Exit fullscreen mode

Notes

  • We must reset debugDefaultTargetPlatformOverride to null by the end of every test case, otherwise Flutter will throw an error;
  • The example here is a bit specific, but you should take the approach and apply to all your widgets.

If you're have any suggestions to improve this example, feel free to share it in the comments.


I hope you enjoyed this post and follow me on any platform for more.

Discussion (4)

Collapse
remejuan profile image
Reme Le Hane

You can simplify you test by using verify instead of running actual logic within the test to update a list.

You end up with the same quality of test by verifying the function was called instead of creating, modifying and expecting on a list.

Even though this is very simply logic, logic should be avoided in tests as there is no test to test that the logic in the test is correct.

Collapse
arthurdenner profile image
Arthur Denner Author

Good point in avoiding logic on tests, thanks for contributing!

Are you referring to Mockito's verify function? If so, does it apply here as I'm dealing with a callback function? I have only used Mockito when testing abstract classes so far.

Collapse
remejuan profile image
Reme Le Hane

It actually does, I wrote a post on how to do that. There is a lot of missing information around testing with flutter right now and we are busy with a larger scale, long term white-label product and realized we have learnt a lot with little help from google.

The only thing I have not worked out yet is how to test the pull to refresh.

The shirt is, pop your callback function into a mock class, then pass that into your widget. You can then verify that.

Thread Thread
arthurdenner profile image
Arthur Denner Author

Cool! I'll check it out. Thanks :)