How to Test Responsive Layouts in Flutter

responsive

Tests

flutter

When you build your app, you sometimes have to make changes on a previously used component that you think is unrelated to some parts of your app you’re potentially breaking when making those changes. Also, when you build your applications to be responsive, meaning the content will adapt to the screen size of the device you use, you want to test if your widgets are correctly displayed for multiple screen sizes. This is where you’ll use widget testing and goldens in Flutter.

In the testWidgets function, you can specify a variant argument, which has the effect of running your test once for each value you specify in this variant. In our case, we have determined certain screen sizes on which we want to ensure our app is rendered properly. For each of those, we specify a variant.

Define your custom sizes variant

For example, let’s suppose that you have 3 screen sizes you want to test your widgets on: an iPhone 8, an iPhone 13 Pro Max and a desktop with a standard 1920x1080 screen. All you have to do is define the following ValueVariant:

final responsiveVariant = ValueVariant<ScreenSize>({
iPhone8,
iPhone13ProMax,
desktop,
});

In order for this to work correctly, you’d have to define the ScreenSize class:

class ScreenSize {
const ScreenSize(this.name, this.width, this.height, this.pixelDensity);
final String name;
final double width, height, pixelDensity;
}

const iPhone8 = ScreenSize('iPhone_8', 414, 736, 3);
const iPhone13ProMax = ScreenSize('iPhone_13_Pro_Max', 414, 896, 3);
const desktop = ScreenSize('Desktop', 1920, 1080, 1);

In the testWidgets function, if you want to specify a personalised screen size and pixel density, you would have to use the binding object of the WidgetTester:

await binding.setSurfaceSize(size)
binding.window.physicalSizeTestValue = size;
binding.window.devicePixelRatioTestValue = pixelDensity;

In order to simplify this, we are going to make an extension on the WidgetTester class:

extension ScreenSizeManager on WidgetTester {
Future<void> setScreenSize(ScreenSize screenSize) async {
return _setScreenSize(
width: screenSize.width,
height: screenSize.height,
pixelDensity: screenSize.pixelDensity,
);
}

Future<void> _setScreenSize({
required double width,
required double height,
required double pixelDensity,
}) async {
final size = Size(width, height);
await binding.setSurfaceSize(size);
binding.window.physicalSizeTestValue = size;
binding.window.devicePixelRatioTestValue = pixelDensity;
}
}

This way, we can use the following command in a testWidgets to set the screen size at our convenience, for example:

tester.setScreenSize(iPhone8);

Wrap the TestWIdgets Function

All we have to do last is to wrap the testWidgets function and call this method each time we want to test a widget on multiple screen sizes, and this way make sure to be consistent and respect our quality standards. Let’s call this function testResponsiveWidgets:

@isTest
void testResponsiveWidgets(
String description,
WidgetTesterCallback callback, {
Future<void> Function(String sizeName, WidgetTester tester)? goldenCallback,
bool? skip,
Timeout? timeout,
bool semanticsEnabled = true,
ValueVariant<ScreenSize>? breakpoints,
dynamic tags,
}) {
final variant = breakpoints ?? responsiveVariant;
testWidgets(
description,
(tester) async {
await tester.setScreenSize(variant.currentValue!);
await callback(tester);
if (goldenCallback != null) {
await goldenCallback(variant.currentValue!.name, tester);
}
},
skip: skip,
timeout: timeout,
semanticsEnabled: semanticsEnabled,
variant: variant,
tags: tags,
);
}

Write your tests

You can now write a single test without defining variants each time you want to test your widgets on multiple screen sizes, and ensure that all our layouts will render properly for each screen size we have defined:

testResponsiveWidgets(
'$Example should render for every screen variants',
(tester) async {
// Make your test here
},
goldenCallback: (sizeName, tester) async {
await expectGoldenMatches(
// Make your golden match here
);
},
);