Zufar Muhamadeev
Zufar Muhamadeev

Reputation: 3212

Widget test fails with No MediaQuery widget found

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.

Upvotes: 87

Views: 138839

Answers (12)

Anandh Krishnan
Anandh Krishnan

Reputation: 5986

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",
              ),
            ),
          ),
        );
      }
    }

Upvotes: 38

Deb
Deb

Reputation: 5649

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');
            },
          );
        },
      ),
    );
  }
}

Upvotes: 0

Saiprasad
Saiprasad

Reputation: 11

Just wrap Scaffold inside MaterialApp, that worked

Upvotes: 1

SoftwareARM
SoftwareARM

Reputation: 1115

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();
  }
}

Upvotes: -1

David Shuma
David Shuma

Reputation: 1399

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())
)

Upvotes: 124

Anoop P K
Anoop P K

Reputation: 11

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..

Upvotes: 0

Hari Prasad Chaudhary
Hari Prasad Chaudhary

Reputation: 64

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

Upvotes: 0

Amit Baderia
Amit Baderia

Reputation: 4882

Replace this

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

with this

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

Upvotes: 6

Tide
Tide

Reputation: 131

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
    )
  }

Upvotes: 10

Amirhossein Ghasemi
Amirhossein Ghasemi

Reputation: 22138

Wrap your widget with MaterialApp in test environment.

Replace this:

await tester.pumpWidget(HomeScreen());

with:

await tester.pumpWidget(MaterialApp(home:HomeScreen()));

Upvotes: 7

Raju Gupta
Raju Gupta

Reputation: 792

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);
 });
} 

Upvotes: 0

Nemanja Knežević
Nemanja Knežević

Reputation: 522

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();

});
}

Upvotes: 27

Related Questions