[英]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(...)?我的问题是关于 flutter 小部件测试,测试现有小部件包装新脚手架(...)的正确方法是什么? I have found MediaQuery.of
but it accepts BuildContext
instead of Widget
.我找到MediaQuery.of
但它接受BuildContext
而不是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?我找到MediaQuery.of
但不明白它如何与现有小部件一起使用? It accept BuildContext
as parameter.它接受BuildContext
作为参数。
You need to wrap your widget with the MediaQuery(...)
instance, and because you are using Scaffold(..)
you must wrap it in a MaterialApp(..)
您需要使用MediaQuery(...)
实例包装您的小部件,并且由于您使用的是Scaffold(..)
您必须将其包装在MaterialApp(..)
Read more about MediaQuery 阅读有关 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.我遇到了同样的问题,我也不得不将它包装在 MaterialApp 中,但我以另一种方式做到了这一点,没有使用 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();
});
}
**A perfect example of how a scaffold() widget should be the child of a MaterialApp() ** ** scaffold() 小部件应该如何成为 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
)
}
I have also faced the same issue and solved by the below method.我也遇到了同样的问题并通过以下方法解决。
Note: I was using bloc.注意:我使用的是 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);
});
}
Wrap your widget with MaterialApp
in test environment.在测试环境中使用MaterialApp
包装您的小部件。
Replace this:替换这个:
await tester.pumpWidget(HomeScreen());
with:和:
await tester.pumpWidget(MaterialApp(home:HomeScreen()));
Replace this替换这个
void main() {
runApp(const MyApp());
}
with this有了这个
void main() {
runApp(
const MaterialApp(
home: MyApp(),
),
);
}
Wrap your widget with MaterialApp()
and pass new class to home
attribute of MaterialApp()
widget.用MaterialApp()
包裹您的小部件并将新类传递给MaterialApp()
小部件的home
属性。
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参考来源: 如何解决 Flutter 中“未找到 MediaQuery 小部件”错误
Wrap your main mathod 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(""),
),
);
}
}
I have tried this inFlutter 2.10.4我试过这个 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..它工作正常..
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();
}
}
Just wrap Scaffold inside MaterialApp, that worked只需将 Scaffold 包裹在 MaterialApp 中,就可以了
yo tuve el mismo error y lo solucione asi yo tuve el mismo error y lo solucione asi
asi me marcaba error asi me marcaba 错误
void main() => runApp(MainPage());
envolvi en un material app envolvi en un material 应用程序
void main() => runApp(MaterialApp(home: MainPage()));
reemplaza nomas estimado saludos reeplaza nomas estimado saludos
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.