An extension function is a member function of a class that is defined outside the class. Extension functions are declared with the receiver type as a prefix to the function’s name. In this case, we are creating an extension function for the WidgetTester class.
So head over to the test folder and create a new file named widget_tester_extension.dart, and add the following code.
extension WidgetTesterExtension on WidgetTester{
}
We will create a setScreenSize function to help us set the screen size for widget testing. We will use the binding object to set the screen size in this function.
Future setScreenSize() async {
binding.window.physicalSizeTestValue = Size(1080, 1920);
binding.window.devicePixelRatioTestValue = 2.625;
binding.platformDispatcher.textScaleFactorTestValue = 1.0;
}
The above function sets the Physical size of the screen to 1080x1920. The device Pixel ratio is the ratio of physical and logical pixels. The textScaleFactor is the text scaling factor for accessibility. We are setting the Pixel ratio to 2.625 and textScaleFactor to 1.0.
But this covers just one more screen size. We need to test for multiple screen sizes. I will create some constants for the screen sizes.
// Google Pixel 2
const double PIXEL_2_HEIGHT = 1920;
const double PIXEL_2_WIDTH = 1080;
// iphone 13
const double IPHONE_13_HEIGHT = 2532;
const double IPHONE_13_WIDTH = 1170;
// iphone 13 device pixel ratio
const double IPHONE_13_PIXEL_RATIO = 3.0;
// galaxy tab
const double GALAXY_TAB_HEIGHT = 2560;
const double GALAXY_TAB_WIDTH = 1600;
// ipad pro pixel ratio
const double IPAD_PRO_PIXEL_RATIO = 4.0;
// pixel 2 pixel ratio
const double PIXEL_2_PIXEL_RATIO = 2.625;
// ipad pro
const double IPAD_PRO_HEIGHT = 2732;
const double IPAD_PRO_WIDTH = 2048;
Now let’s create a model class that will hold the screen size and the pixel ratio information and makes it easier for us to call.
So outside the extension function, let’s create a class named TestCaseScreenInfo and add the following code.
class TestCaseScreenInfo {
final String deviceName;
final Size screenSize;
final double pixedRatio;
final double textScaleValue;
const TestCaseScreenInfo(
{required this.deviceName,
required this.screenSize,
required this.pixedRatio,
required this.textScaleValue});
}
We have the model class and the constants for the screen sizes. Let’s create a list of TestCaseScreenInfo objects which will hold the screen size information for all the devices we want to test.
const testCaseScreenInfoList = [
// Pixel 2 Portrait
TestCaseScreenInfo(
deviceName: 'Pixel-2-Portrait',
screenSize: Size(PIXEL_2_WIDTH, PIXEL_2_HEIGHT),
pixedRatio: PIXEL_2_PIXEL_RATIO,
textScaleValue: 1.0),
// Pixel 2 Landscape
TestCaseScreenInfo(
deviceName: 'Pixel-2-Landscape',
screenSize: Size(PIXEL_2_HEIGHT, PIXEL_2_WIDTH),
pixedRatio: PIXEL_2_PIXEL_RATIO,
textScaleValue: 1.0),
// iphone 13 Portrait
TestCaseScreenInfo(
deviceName: 'iphone-13-Portrait',
screenSize: Size(IPHONE_13_WIDTH, IPHONE_13_HEIGHT),
pixedRatio: IPHONE_13_PIXEL_RATIO,
textScaleValue: 1.0),
// iphone 13 Landscape
TestCaseScreenInfo(
deviceName: 'iphone-13-Landscape',
screenSize: Size(IPHONE_13_HEIGHT, IPHONE_13_WIDTH),
pixedRatio: IPHONE_13_PIXEL_RATIO,
textScaleValue: 1.0),
// ipad pro Portrait
TestCaseScreenInfo(
deviceName: 'ipad-pro-Portrait',
screenSize: Size(IPAD_PRO_WIDTH, IPAD_PRO_HEIGHT),
pixedRatio: IPAD_PRO_PIXEL_RATIO,
textScaleValue: 1.0),
// ipad pro Landscape
TestCaseScreenInfo(
deviceName: 'ipad-pro-Landscape',
screenSize: Size(IPAD_PRO_HEIGHT, IPAD_PRO_WIDTH),
pixedRatio: IPAD_PRO_PIXEL_RATIO,
textScaleValue: 1.0),
// galaxy tab Portrait
TestCaseScreenInfo(
deviceName: 'galaxy-tab-Portrait',
screenSize: Size(GALAXY_TAB_WIDTH, GALAXY_TAB_HEIGHT),
pixedRatio: IPAD_PRO_PIXEL_RATIO,
textScaleValue: 1.0),
// galaxy tab Landscape
TestCaseScreenInfo(
deviceName: 'galaxy-tab-Landscape',
screenSize: Size(GALAXY_TAB_HEIGHT, GALAXY_TAB_WIDTH),
pixedRatio: IPAD_PRO_PIXEL_RATIO,
textScaleValue: 1.0),
];
Now I will pass the newly created testCaseScreenInfoList to the setScreenSize function and set the screen size from the class value.
Future setScreenSize(TestCaseScreenInfo testCaseScreenInfo) async {
binding.platformDispatcher.textScaleFactorTestValue =
testCaseScreenInfo.textScaleValue;
binding.window.physicalSizeTestValue = testCaseScreenInfo.screenSize;
binding.window.devicePixelRatioTestValue = testCaseScreenInfo.pixedRatio;
//Reset
addTearDown(binding.window.clearPhysicalSizeTestValue);
}
addTearDown is a function that will be called after the test is completed. We use this function to reset the screen size to the default value.
Let’s create a function that will take the screen size information and the widget tester to run the Widget test for multiple screen sizes.
Future testMultipleScreenSize(String testName,
Future<void> Function(WidgetTester, TestCaseScreenInfo testCase),
testFunction) async =>testCaseScreenInfoList.forEach((testCase) {
});
The above function takes the test name, the test function and the widget tester. We will call the forEach loop and run the Widget test for each screen from our list. Making our function like this.
Future testWidgetsMultipleScreenSizes(
String testName,
Future<void> Function(WidgetTester, TestCaseScreenInfo testCase)
testFunction) async =>
testCaseScreenInfoList.forEach((testCase) async {
testWidgets("$testName-${testCase.deviceName}", (tester) async {
await tester.setScreenSize(testCase);
await testFunction(tester, testCase);
});
});
That’s a lot of code. In the following video, we will create a common function to generate Goldens. We will also class the newly created testWidgetsMultipleScreenSizes function to run the Widget test for multiple screen sizes.