How To Test Your Responsive Flutter App

Flutter category image

Introduction

Flutter web apps become more and more popular and the Flutter team has made significant improvements over the last years to the platform.

I also started using it more as a solution. Some examples are my advanced QuickCoder article search, my time tracker Foklarity, and my advanced Gumroad dashboard GDash for Gumroad power sellers.

A good web app needs to be responsive these days. Flutter offers some solutions here (LayoutBuilder, MediaQuery), but testing still feels hard.

I came up with some solutions myself which I want to share. So here is how to test your responsive Flutter app.

Breakpoints

This article focuses on widget testing since this is the layer that handles the responsiveness. The basic idea is to run every test with a different resolution with minimal effort. I use this little helper in various forms:

Dart
class TestScreenSizes {
  static final mobile = TestScreenSize(
    'Mobile',
    Size(UiBreakpoints.mobileWindowWidth, UiBreakpoints.mobileWindowHeight),
  );
  static final tablet = TestScreenSize(
    'Tablet',
    Size(UiBreakpoints.tabletWindowWidth, UiBreakpoints.tabletWindowHeight),
  );
  static final desktop = TestScreenSize(
    'Desktop',
    Size(UiBreakpoints.desktopWindowWidth, UiBreakpoints.desktopWindowHeight),
  );
  static final fullHd = TestScreenSize(
    'FullHD',
    Size(UiBreakpoints.fullHdWindowWidth, UiBreakpoints.fullHdWindowHeight),
  );

  static final all = [mobile, tablet, desktop, fullHd];
}

class TestHelper {
  static Future<void> setScreenSize(
    WidgetTester tester,
    TestScreenSize screenSize,
  ) async {
    await tester.binding.setSurfaceSize(screenSize.size);
    await tester.pumpAndSettle();
  }
}

class TestScreenSize {
  final String name;
  final Size size;

  const TestScreenSize(this.name, this.size);
}

class UiBreakpoints {
  static double fullHdWindowWidth = 1920;
  static double fullHdWindowHeight = 1080;

  static double desktopWindowWidth = 1200;
  static double desktopWindowHeight = 800;

  static double tabletWindowWidth = 768;
  static double tabletWindowHeight = 1024;

  static double mobileWindowWidth = 375;
  static double mobileWindowHeight = 667;
}

I defined four screen sizes with common dimensions for mobile, tablet, desktop, and full HD resolutions. Depending on how many breakpoints you have in your app, not all might be needed.

LayoutBuilder is usually my widget of choice to distinguish between those breakpoints. 

Testing

As mentioned earlier, I run all tests in every resolution. Here is a snippet of how it works:

Dart
void main() {
  group("Product List", () {
    for (final size in TestScreenSizes.all) {
      testWidgets("Test default elements  (${size.name})", (tester) async {
        await TestHelper.setScreenSize(tester, size);

        await tester.pumpWidget(SomeWidget());
        await tester.pumpAndSettle();

        expect(find.byType(GroupHeaderWidget), findsOneWidget);
        expect(find.byType(ProductTableHeaderWidget), findsOneWidget);
        expect(find.byType(ProductTableWidget), findsOneWidget);
        expect(find.byType(FilterOptionsWidget), findsOneWidget);
        expect(find.byType(ExportDataWidget), findsOneWidget);
        expect(find.byType(JumpToStartWidget), findsOneWidget);
        expect(find.byType(GroupHeaderWidget), findsOneWidget);
        expect(find.byType(Badge), findsNothing);
      });
    }
  });
}

There is a for loop that iterates over all sizes and inside the test, I set the dimensions at the beginning with the setSurfaceSize method. The test name includes the screen size so that I can identify failing ones better if they only fail with certain resolutions.

Basically, I am wrapping all my tests in a loop and set the size at the start. This already covers the majority of issues. But now, let’s move on to fine tuning.

Drawbacks

Widget tests aren’t perfect. I had many problems with them in the past (#1, #2). But during responsive testing, you enter a different hell: RenderFlex Overflow errors.

It can happen that you get these in your tests but they don’t appear in your running app. Fixing them is usually the best solution but in some cases, it’s not possible or not worth the effort. Then, your only solution is to ignore them and consider them as false positives. Here is how (thanks to Eduardo Yamauchi on Stackoverflow):

Dart
FlutterError.onError = _onError_ignoreOverflowErrors;

Function _onError_ignoreOverflowErrors = (
  FlutterErrorDetails details, {
  bool forceReport = false,
}) {
  assert(details != null);
  assert(details.exception != null);
  // ---

  bool ifIsOverflowError = false;

  // Detect overflow error.
  var exception = details.exception;
  if (exception is FlutterError)
    ifIsOverflowError = !exception.diagnostics
        .any((e) => e.value.toString().startsWith("A RenderFlex overflowed by"));

  // Ignore if is overflow error.
  if (ifIsOverflowError)
    print('Overflow error.');

  // Throw others errors.
  else
    FlutterError.dumpErrorToConsole(details, forceReport: forceReport);
};

The downside is that you cannot set this globally or in a setUp method. You need to call it in every test. And it will also cover real overflow errors. So maybe it’s a good thing that you cannot ignore them globally.

Another downside of my approach is that when you have different widgets for mobile and desktop, expecting gets messy. You end up with many comparisons like these:

Dart
expect(find.byType(SomeWidget), 
    size == TestScreenSizes.mobile ? findsNothing : findsOneWidget);

When you first start introducing responsive testing, you’ll spent a lot of time fixing those errors. On the other hand, you can be sure that your responsive design is tested to some extent at least.

It depends on you and your app if these kinds of tests are worth it or not.

Conclusion

Responsive testing is messy in Flutter. In this article, I showed you my approach of how to test your responsive Flutter app. It’s not perfect, but it gets the job done. For complex apps, I would recommend a responsive testing strategy any time.


Want More Flutter Content?

Join my bi-weekly newsletter that delivers small Flutter portions right in your inbox. A title, an abstract, a link, and you decide if you want to dive in!

Flutter ❤️ Firebase

Get started with Firebase and learn how to use it in your Flutter apps. My detailed ebook delivers you everything you need to know! Flutter and Firebase are a perfect match!

Become A Testing Expert!

Become a proficient Flutter app tester with my detailed guide. This ebook covers everything from unit tests over widget tests up to dependency mocking.