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.
To solve this No MediaQuery widget found Error You Must have to use MaterialApp() to use MediaQuery. You cant use MediaQuery without MaterialApp Widget To use MediaQuery. of() function, first, you need to wrap your widget with MaterialApp(), even after that, you may get the error.
MaterialApp is a widget that introduces a number of widgets Navigator, Theme that are required to build a material design app. Scaffold Widget is used under MaterialApp, it gives you many basic functionalities, like AppBar, BottomNavigationBar, Drawer, FloatingActionButton, etc.
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()) )
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(); }); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With