42

I have a Flutter widget which shows extra data depending on the screen size. Does anyone know a way of testing this widget on multiple different screen sizes?

I've had a look through the widget_tester source code but can't find anything.

Jordan Davies
  • 9,925
  • 6
  • 40
  • 51

8 Answers8

67

You can specify custom surface size by using WidgetTester

The following code will run a test with a screen size of 42x42

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets("foo", (tester) async {
    tester.binding.window.physicalSizeTestValue = Size(42, 42);

    // resets the screen to its original size after the test end
    addTearDown(tester.binding.window.clearPhysicalSizeTestValue);

    // TODO: do something
  });
}
Burhanuddin Rashid
  • 5,260
  • 6
  • 34
  • 51
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • If a widget with Scaffold parent is under test it is required to add Material app as a wrapper like this: await tester.pumpWidget(new MaterialApp(home: new LoginScreen())); – Valentina Konyukhova May 06 '19 at 12:10
  • What's the default size? I did 1200, 2400 and it's still smaller than the default size. – Luke Pighetti Feb 26 '20 at 02:27
  • It's a width of 800 and height of 600 – Rémi Rousselet Feb 26 '20 at 03:53
  • @RémiRousselet I followed your advice but it does not work properly with texts – TSR Jun 18 '20 at 10:32
  • @RémiRousselet check out https://stackoverflow.com/questions/62447898/flutter-widget-test-cannot-emulate-different-screen-size-properly and https://github.com/flutter/flutter/issues/59755 – TSR Jun 18 '20 at 10:32
  • You can also set the pixel density using the `devicePixelRatioTestValue` property – Ber Jan 04 '21 at 11:21
  • 5
    Proper answer now is to use `tester.binding.setSurfaceSize(size);` (see https://github.com/flutter/flutter/issues/12994#issuecomment-902964066) – Dominik Roszkowski Feb 14 '22 at 12:04
  • tester.binding.window is deprecated from 3.9.0 This seems to work tester.view.devicePixelRatio = 1; tester.view.physicalSize = const Size(1200, 800); – manish kiranagi Jun 21 '23 at 21:27
37

Not sure why but solution of @rémi-rousselet didn't work for me. I've had to specify screen size using binding.window.physicalSizeTestValue and binding.window.devicePixelRatioTestValue so that output is fully deterministic

I've added a little bit more code for flutter beginners like me. Check this:

void main() {

  final TestWidgetsFlutterBinding binding =
    TestWidgetsFlutterBinding.ensureInitialized();

  testWidgets("Basic layout test (mobile device)", (tester) async {
    binding.window.physicalSizeTestValue = Size(400, 200);
    binding.window.devicePixelRatioTestValue = 1.0;

    await tester.pumpWidget(new MyApp());

    expect(find.byType(MyHomePage), findsOneWidget);
    // etc.
  });
}
VizGhar
  • 3,036
  • 1
  • 25
  • 38
  • 1
    Thanks, this one worked for me (setSurfaceSize didn't). – divan Sep 05 '19 at 22:29
  • 1
    Same here, setSurfaceSize() didn't work for me, but your solution did. Thanks. – svprdga Oct 11 '19 at 09:05
  • @VizGhar I followed your advice but I does not work properly with texts check out https://stackoverflow.com/questions/62447898/flutter-widget-test-cannot-emulate-different-screen-size-properly and https://github.com/flutter/flutter/issues/59755 – TSR Jun 18 '20 at 10:33
  • adding `devicePixelRatioTestValue` works well! – Moses Aprico Mar 14 '21 at 16:29
  • For anyone who gets a type casting error when trying this, add `as TestWidgetsFlutterBinding` So the whole thing would be `final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;` – Loren.A Nov 01 '21 at 01:25
15

There is a package called device_preview that can simulate your flutter app running on different devices.

Pegasis
  • 1,256
  • 11
  • 15
9

@rémi-rousselet's solution works perfectly!

In addition if you want to test an orientation change, try this:

const double PORTRAIT_WIDTH = 400.0;
const double PORTRAIT_HEIGHT = 800.0;
const double LANDSCAPE_WIDTH = PORTRAIT_HEIGHT;
const double LANDSCAPE_HEIGHT = PORTRAIT_WIDTH;

final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();

await binding.setSurfaceSize(Size(PORTRAIT_WIDTH, PORTRAIT_HEIGHT));
await tester.pumpWidget(MyWidget());

// test in portrait

await binding.setSurfaceSize(Size(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT));
await tester.pumpAndSettle();

// OrientationBuilder gets triggered

// test in landscape
Mark
  • 520
  • 1
  • 7
  • 21
8

Currently the safest way is to use setSurfaceSize

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets("foo", (tester) async {
    tester.binding.setSurfaceSize(Size(400, 400));

    // reset
    tester.binding.setSurfaceSize(null);

    // continue
  });
}

See here for related Github issue

Dominik Roszkowski
  • 2,715
  • 1
  • 19
  • 46
5

Since version 3.10.0, the window singleton is deprecated( https://docs.flutter.dev/release/breaking-changes/window-singleton). Therefore, the size must now be set as follows:

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets("foo", (tester) async {
    tester.view.physicalSize = Size(42, 42);

    // resets the screen to its original size after the test end
    addTearDown(tester.view.resetPhysicalSize);

    // TODO: do something
  });
}

MCB
  • 503
  • 1
  • 8
  • 21
3

Although @Rémi Rousselet's answer was very helpful it didn't completely solve my problem. It turns out that I could just wrap my widget under test in a MediaQuery widget and set the size.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  Widget makeTestableWidget({Widget child, Size size}) {
    return MaterialApp(
      home: MediaQuery(
        data: MediaQueryData(size: size),
        child: child,
      ),
    );
  }

  testWidgets("tablet", (tester) async {
    final testableWidget = makeTestableWidget(
      child: WidgetUnderTest(),
      size: Size(1024, 768),
    );

    ...
  });

  testWidgets("phone", (tester) async {
    final testableWidget = makeTestableWidget(
      child: WidgetUnderTest(),
      size: Size(375, 812),
    );

    ...
  });
}
Jordan Davies
  • 9,925
  • 6
  • 40
  • 51
  • 1
    That's not exactly true. It doesn't truly change the size, but mock MediaQuery. Things like `LayoutBuilder` or `RenderObjects`, or golden tests will still be based on the default size. – Rémi Rousselet Dec 11 '18 at 23:22
  • ahh ok, when I tried using TestWidgetsFlutterBinding it had no impact because in my widget, I get the screen size from the MediaQuery which was being created my the MaterialApp widget. If I remove the MaterialApp widget it throws an error about textDirection being null, so this seemed like the best option but obviously not the best option in every case. – Jordan Davies Dec 12 '18 at 08:54
  • I'd suggest marking my answer as the solution instead (unless you disagree), as mocking MediaQuery is rarely desired the solution. – Rémi Rousselet Dec 19 '18 at 09:36
3

You could try this widget to test your widgets changing screen size in realtime

Screen Size Test

https://pub.dev/packages/screen_size_test

Preview

enter image description here

Demo https://dartpad.dartlang.org/43d9c47a8bf031ce3ef2f6314c9dbd52

Code Sample

import 'package:screen_size_test/screen_size_test.dart';
...
MaterialApp(
  title: 'Demo',
  builder: (context, child) => ScreenSizeTest(
    child: child,
  ),
  home: Scaffold(
    body: ListView(
      children: List.generate(
          20,
          (index) => Container(
                padding: EdgeInsets.all(10),
                child: Placeholder(),
              )),
    ),
  ),
)