Widget test fails with No MediaQuery widget found

FlutterDartFlutter Test

Flutter Problem Overview


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.

Flutter Solutions


Solution 1 - Flutter

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

Solution 2 - Flutter

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

});
}

Solution 3 - Flutter

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

Solution 4 - Flutter

Wrap your widget with MaterialApp in test environment.

Replace this:

await tester.pumpWidget(HomeScreen());

with:

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

Solution 5 - Flutter

Wrap your main method

from this

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

to

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

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(
                "Salezrobot",
                style: TextStyle(
                  fontSize: 24,
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                  fontFamily: 'HelveticaNeue',
                ),
              ),
            ),
            body: Text(""),
          ),
        );
      }
    }

Solution 6 - Flutter

Replace this

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

with this

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

Solution 7 - Flutter

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

Solution 8 - Flutter

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

Solution 9 - Flutter

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZufar MuhamadeevView Question on Stackoverflow
Solution 1 - FlutterDavid ShumaView Answer on Stackoverflow
Solution 2 - FlutterNemanja KneževićView Answer on Stackoverflow
Solution 3 - FlutterTideView Answer on Stackoverflow
Solution 4 - FlutterAmir Hossein GhasemiView Answer on Stackoverflow
Solution 5 - FlutterAnandh KrishnanView Answer on Stackoverflow
Solution 6 - FlutterAmit BaderiaView Answer on Stackoverflow
Solution 7 - FlutterRaju GuptaView Answer on Stackoverflow
Solution 8 - FlutterHari Prasad ChaudharyView Answer on Stackoverflow
Solution 9 - FlutterAnoop P KView Answer on Stackoverflow