Widget test fails with No MediaQuery widget found
Asked Answered
D

12

86

My question is about flutter widget test, what is proper way to test existing widgets wrapped new Scaffold(...)? I have found MediaQuery.of but it accepts BuildContext instead of Widget.

Details: I have wrote simple login form widget and trying to implement widget test for it. After executing test i got exception:

Expected: 'Sorry, only customer can login from mobile device. [Mock]'
  Actual: FlutterError:<No MediaQuery widget found.
          Scaffold widgets require a MediaQuery widget ancestor.
          The specific widget that could not find a MediaQuery ancestor was:
            Scaffold-[LabeledGlobalKey<ScaffoldState>#8ffee]
          The ownership chain for the affected widget is:
            Scaffold-[LabeledGlobalKey<ScaffoldState>#8ffee] ← LoginForm ← [root]
          Typically, the MediaQuery widget is introduced by the MaterialApp or WidgetsApp widget at
          the top of your application widget tree.>
   Which: FlutterError:<No MediaQuery widget found.
          Scaffold widgets require a MediaQuery widget ancestor.
          The specific widget that could not find a MediaQuery ancestor was:
            Scaffold-[LabeledGlobalKey<ScaffoldState>#8ffee]
          The ownership chain for the affected widget is:
            Scaffold-[LabeledGlobalKey<ScaffoldState>#8ffee] ← LoginForm ← [root]
          Typically, the MediaQuery widget is introduced by the MaterialApp or WidgetsApp widget at
          the top of your application widget tree.>is not a string

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (C:\Work\app_mobile\test\login_widget_test.dart:21:5)
<asynchronous suspension>
#5      testWidgets.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flutter_test\src\widget_tester.dart:61:25)
#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test\src\binding.dart:471:19)
<asynchronous suspension>
#9      TestWidgetsFlutterBinding._runTest (package:flutter_test\src\binding.dart:458:14)
#10     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test\src\binding.dart:640:24)
#11     _FakeAsync.run.<anonymous closure> (package:quiver\testing\src\async\fake_async.dart:186:24)
#15     _FakeAsync.run (package:quiver\testing\src\async\fake_async.dart:185:11)
#16     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test\src\binding.dart:638:16)
#17     testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test\src\widget_tester.dart:60:24)
#18     Declarer.test.<anonymous closure>.<anonymous closure> (package:test\src\backend\declarer.dart:160:19)
<asynchronous suspension>
#19     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test\src\backend\invoker.dart:206:15)
<asynchronous suspension>
#23     Invoker.waitForOutstandingCallbacks (package:test\src\backend\invoker.dart:203:5)
#24     Declarer.test.<anonymous closure> (package:test\src\backend\declarer.dart:158:29)
<asynchronous suspension>
#25     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test\src\backend\invoker.dart:351:23)
<asynchronous suspension>
#27     StackZoneSpecification._run (package:stack_trace\src\stack_zone_specification.dart:209:15)
#28     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace\src\stack_zone_specification.dart:119:48)
#33     StackZoneSpecification._run (package:stack_trace\src\stack_zone_specification.dart:209:15)
#34     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace\src\stack_zone_specification.dart:119:48)
#39     _Timer._runTimers (dart:isolate-patch/dart:isolate/timer_impl.dart:367)
#40     _Timer._handleMessage (dart:isolate-patch/dart:isolate/timer_impl.dart:401)
#41     _RawReceivePortImpl._handleMessage (dart:isolate-patch/dart:isolate/isolate_patch.dart:163)
(elided 17 frames from package dart:async and package dart:async-patch)

Here is Login form widget:

import 'dart:async';
import 'dart:convert';

import 'package:app_facade/app_facade.dart';
import 'package:app_mobile/utils/dependency_injection.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/services.dart';

class LoginForm extends StatefulWidget {
  const LoginForm({ Key key }) : super(key: key);

  static GlobalKey<FormFieldState<String>> emailFieldKey = new GlobalKey<FormFieldState<String>>();
  static GlobalKey<FormFieldState<String>> passwordFieldKey = new GlobalKey<FormFieldState<String>>();
  static const String routeName = '/';

  @override
  LoginFormState createState() => new LoginFormState();
}

class LoginData {
  String email = '';
  String password = '';
}

class LoginFormState extends State<LoginForm> {
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

  LoginData loginData = new LoginData();

  UserApi _userApi;

  void showInSnackBar(String value) {
    _scaffoldKey.currentState.showSnackBar(new SnackBar(
        content: new Text(value)
    ));
  }

  bool _autovalidate = false;
  bool _formWasEdited = false;
  final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();


  @override
  void initState() {
    super.initState();
    _userApi = new Injector().userApi;
  }

  Future<Null> _handleSubmitted() async {
    final FormState form = _formKey.currentState;
    if (!form.validate()) {
      _autovalidate = true;  // Start validating on every change.
      showInSnackBar('Please fix the errors in red before submitting.');
    } else {
      form.save();
      login();
    }
  }

  Future<Null> login() async {
    try {
      await _userApi.login(loginData.email, loginData.password);
      Navigator.popAndPushNamed(context, '/user');
    } catch (e) {
      showInSnackBar(e.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: _scaffoldKey,
      appBar: new AppBar(
        title: const Text('Some'),
      ),
      body: new Form(
          key: _formKey,
          autovalidate: _autovalidate,
          child: new ListView(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            children: <Widget>[
              new TextFormField(
                key: new Key('email'),
                decoration: const InputDecoration(
                  icon: const Icon(Icons.person),
                  hintText: 'Your email',
                  labelText: 'Email *',
                ),
                onSaved: (String value) { loginData.email = value; },
              ),
              new TextFormField(
                key: LoginForm.passwordFieldKey,
                decoration: const InputDecoration(
                  icon: const Icon(Icons.security),
                  hintText: 'Your password',
                  labelText: 'Password *',
                  ),
                obscureText: true,
                onSaved: (String value) { loginData.password = value; },
              ),
              new Container(
                padding: const EdgeInsets.all(20.0),
                alignment: const FractionalOffset(0.5, 0.5),
                child: new RaisedButton(
                  child: const Text('SUBMIT'),
                  onPressed: _handleSubmitted,
                ),
              ),
              new Container(
                padding: const EdgeInsets.only(top: 20.0),
                child: new Text('* indicates required field', style: Theme.of(context).textTheme.caption),
              ),
            ],
          )
      ),
    );
  }
}

And here is widget test:

import 'package:app_facade/app_facade.dart';
import 'package:app_mobile/login_form.dart';
import 'package:app_mobile/utils/dependency_injection.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('login widget test', (WidgetTester tester) async {
    Injector.configure(BackendType.MOCK);
    // Tells the tester to build a UI based on the widget tree passed to it
    var loginForm = new LoginForm();
    await tester.pumpWidget(
      loginForm
    );

    tester.enterText(find.byKey(LoginForm.emailFieldKey), "login");
    tester.enterText(find.byKey(LoginForm.passwordFieldKey), "password");

    var exception = tester.takeException();
    print(exception);
    expect(exception, equals('Sorry, only customer can login from mobile device. [Mock]'));
  });
}

I have found MediaQuery.of but don't understand how can it be used with existing widget? It accept BuildContext as parameter.

Donatus answered 29/1, 2018 at 10:8 Comment(0)
H
121

You need to wrap your widget with the MediaQuery(...) instance, and because you are using Scaffold(..) you must wrap it in a MaterialApp(..)

Read more about MediaQuery

Example:

Widget testWidget = new MediaQuery(
      data: new MediaQueryData(),
      child: new MaterialApp(home: new LoginForm())
)
Hanoverian answered 29/1, 2018 at 12:1 Comment(3)
Is there an advantage to your solution over that proposed by @nknezevic below? The doc for MediaQuery states that MaterialApp and WidgetsApp introduces a MediaQuery. I am a noob, so would really like to understand. Otherwise, the answer below seems preferable for it is a cleaner, simpler solution, imo.Clareta
or just simply wrap the LoginForm with a MaterialApp . That should do it like testWidget = MaterialApp(home: LoginForm());Journalism
Since Flutter 3.10 this solution will fail if you use MediaQuery.of(context).size - it will return zero, so just wrap your main method with MaterialApp() as suggested belowSeemaseeming
K
38

Wrap your main method with MaterialApp()

from this

     void main() {
      runApp( 
            YourScreen(),
         );
    }

to

        void main() {
      runApp(
        MaterialApp(
          home: YourScreen(),
        ),
      );
    }

My full code

        void main() {
      runApp(
        MaterialApp(
          home: LoginScreen(),
        ),
      );
    }
    
    class LoginScreen extends StatefulWidget {
      @override
      _LoginScreen createState() => _LoginScreen();
    }
    
    class _LoginScreen extends State<LoginScreen> {
    
      @override
      void initState() {
        super.initState();
      }
    
    
      @override
      Widget build(BuildContext context) {
          return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text(
                "Your App",
              ),
            ),
          ),
        );
      }
    }
Kastner answered 20/1, 2022 at 12:22 Comment(0)
D
27

I had same problem and I also had to wrap it in MaterialApp, but I did that in a bit other way, without using MediaQuery. In my case it works

void main() {

Widget createWidgetForTesting({Widget child}){
return MaterialApp(
  home: child,
);
}

testWidgets('Login Page smoke test', (WidgetTester tester) async {

await tester.pumpWidget(createWidgetForTesting(child: new LoginPage()));

await tester.pumpAndSettle();

});
}
Desilva answered 17/5, 2019 at 10:19 Comment(0)
I
10

A perfect example of how a scaffold() widget should be the child of a MaterialApp()

import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'title'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key Key, this.title}) : super(key: Key);

  @override
  _MyHomePageState createState() => _MyHomePageState();

}

class _MyHomePageState extends State<MyHomePage> {
  Widget build(BuildContext context) {
    return new Scaffold
    (
        appBar: AppBar(
    backgroundColor: Colors.transparent,
    title: widget.title
    )
  }
Imena answered 28/3, 2021 at 22:31 Comment(0)
L
7

Wrap your widget with MaterialApp in test environment.

Replace this:

await tester.pumpWidget(HomeScreen());

with:

await tester.pumpWidget(MaterialApp(home:HomeScreen()));
Lewse answered 23/8, 2021 at 10:22 Comment(0)
W
6

Replace this

void main() {
  runApp(const MyApp());
}

with this

void main() {
  runApp(
    const MaterialApp(
      home: MyApp(),
    ),
  );
}
Witch answered 2/12, 2021 at 2:43 Comment(0)
R
1

Just wrap Scaffold inside MaterialApp, that worked

Reliquary answered 20/11, 2022 at 9:22 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Emmuela
U
0

I have also faced the same issue and solved by the below method.

Note: I was using bloc.

void main() {
 testWidgets('Find an app bar with name of weather search',
  (WidgetTester tester) async {
 await tester.pumpWidget(BlocProvider(
  create: (context) => WeatherBloc(WeatherRepo()),
  child: const MaterialApp(
  home: CounterHomePage(),
  ),
 ));

 expect(find.text('Weather Search'), findsOneWidget);
 });
} 
Uteutensil answered 13/8, 2021 at 8:15 Comment(0)
U
0

Wrap your widget with MaterialApp() and pass new class to home attribute of MaterialApp() widget.

void main(){
  runApp(MyApp());
}
    
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp( //use MaterialApp() widget like this
      home: Home() //create new widget class for this 'home' to 
                   // escape 'No MediaQuery widget found' error
    );
  }
}

Reference From: How to Solve ’No MediaQuery widget found’ Error in Flutter

Unassuming answered 2/12, 2021 at 16:54 Comment(0)
Z
0

I have tried this inFlutter 2.10.4

Widget createWidgetForTesting({required Widget child}) {
    return MediaQuery(
          data: const MediaQueryData(),
          child:MaterialApp(home: Scaffold(body: child)));
}
testWidgets('Test todo title and description', (WidgetTester tester) async {
      final todo = MockData.mockTodosData[0];
      await tester
          .pumpWidget(createWidgetForTesting(child: TodoWidget(item: todo)));
      final titleFinder = find.text(todo.title);
      final descFinder = find.text(todo.desc);
      expect(titleFinder, findsOneWidget);
      expect(descFinder, findsOneWidget);
    });

It works fine..

Zoomorphism answered 14/4, 2022 at 13:10 Comment(0)
T
0

Refactor your code a little bit so that the context you are passing is not of a Widget above MaterialApp. For example:

Instead Of:

import 'package:flutter/material.dart';

void main() => runApp(const BottomSheetApp());

class BottomSheetApp extends StatelessWidget {
  const BottomSheetApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Bottom Sheet Sample')),
        body: Center(
          child: ElevatedButton(
            child: const Text('Open Buttom Sheet'),
            onPressed: () {
              showModalBottomSheet<void>(
                context: context,
                builder: (context) {
                  return Text('My Sheet');
                },
              );
            },
          ),
        ),
      ),
    );
  }
}

Use:

import 'package:flutter/material.dart';

void main() => runApp(const BottomSheetApp());

class BottomSheetApp extends StatelessWidget {
  const BottomSheetApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Bottom Sheet Sample')),
        body: const BottomSheetExample(), // Move your code to a separate widget
      ),
    );
  }
}

class BottomSheetExample extends StatelessWidget {
  const BottomSheetExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: const Text('Open Buttom Sheet'),
        onPressed: () {
          showModalBottomSheet<void>(
            context: context,
            builder: (context) {
              return Text('My Sheet');
            },
          );
        },
      ),
    );
  }
}
Tanberg answered 20/3, 2023 at 13:23 Comment(0)
C
-1

In my case I recommend this starting scheme

import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

//void main() => runApp(HomePage());
void main() {
  //Initialize ScreenUtil
  ScreenUtil.ensureScreenSize();
  setPathUrlStrategy();
  runApp(HomePage());
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  MyApp createState() => MyApp();
}

class MyApp extends State<HomePage> {
  @override
  void initState() {
    super.initState();
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    MaterialApp materialApp = MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'TITLE APP',
      theme: ThemeData(
        primaryColor: kPrimaryColor,
        scaffoldBackgroundColor: Color.fromARGB(255, 0, 0, 0),
      ),
      home: InicioScreen(
        context: context,
      ),
      onGenerateRoute: (RouteSettings settings) {
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(
                builder: (context) => Screen1(context: this.context));
            break;
          case '/cartelera':
            return MaterialPageRoute(
              builder: (context) => Screen2(context: this.context),
            );
            break;
        }
      },
    );

    return MediaQuery(
        data: new MediaQueryData(),
        child: LayoutBuilder(
          builder: (context, constraints) {
            return OrientationBuilder(
              builder: (context, orientation) {
                ScreenUtil.init(context);
                // SizerUtil().init(constraints, orientation);
                return new MaterialApp(home: materialApp);
              },
            );
          },
        ));
  }

  @override
  void dispose() {
    super.dispose();
  }
}
Cistern answered 6/7, 2022 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.