小部件测试失败,未找到 MediaQuery 小部件
Widget test fails with No MediaQuery widget found
我的问题是关于 flutter 小部件测试,测试现有小部件包装 new Scaffold(...) 的正确方法是什么?我找到了 MediaQuery.of
但它接受 BuildContext
而不是 Widget
.
详情:我已经编写了简单的登录表单小部件并尝试为其实现小部件测试。执行测试后我得到异常:
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)
这是登录表单小部件:
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),
),
],
)
),
);
}
}
这里是插件测试:
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]'));
});
}
我找到了 MediaQuery.of
但不明白它如何与现有小部件一起使用?它接受 BuildContext
作为参数。
您需要使用 MediaQuery(...)
实例包装小部件,并且因为您使用的是 Scaffold(..)
,所以必须将其包装在 MaterialApp(..)
中
示例:
Widget testWidget = new MediaQuery(
data: new MediaQueryData(),
child: new MaterialApp(home: new LoginForm())
)
我遇到了同样的问题,我也不得不将它包装在 MaterialApp 中,但我用其他方式做到了,没有使用 MediaQuery。就我而言,它有效
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();
});
}
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
)
}
我也遇到了同样的问题,用下面的方法解决了。
注意:我使用的是 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);
});
}
在测试环境中用 MaterialApp
包装您的小部件。
替换为:
await tester.pumpWidget(HomeScreen());
与:
await tester.pumpWidget(MaterialApp(home:HomeScreen()));
替换这个
void main() {
runApp(const MyApp());
}
有了这个
void main() {
runApp(
const MaterialApp(
home: MyApp(),
),
);
}
用 MaterialApp()
包装您的小部件,并将新的 class 传递给 MaterialApp()
小部件的 home
属性。
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
);
}
}
参考自:How to Solve ’No MediaQuery widget found’ Error in Flutter
包装您的主要方法
由此
void main() {
runApp( YourScreen(),
);
}
到
void main() {
runApp(
MaterialApp(
home: LoginScreen(),
),
);
}
我的完整代码
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(""),
),
);
}
}
我已经在 Flutter 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);
});
它工作正常..
我的问题是关于 flutter 小部件测试,测试现有小部件包装 new Scaffold(...) 的正确方法是什么?我找到了 MediaQuery.of
但它接受 BuildContext
而不是 Widget
.
详情:我已经编写了简单的登录表单小部件并尝试为其实现小部件测试。执行测试后我得到异常:
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)
这是登录表单小部件:
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),
),
],
)
),
);
}
}
这里是插件测试:
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]'));
});
}
我找到了 MediaQuery.of
但不明白它如何与现有小部件一起使用?它接受 BuildContext
作为参数。
您需要使用 MediaQuery(...)
实例包装小部件,并且因为您使用的是 Scaffold(..)
,所以必须将其包装在 MaterialApp(..)
示例:
Widget testWidget = new MediaQuery(
data: new MediaQueryData(),
child: new MaterialApp(home: new LoginForm())
)
我遇到了同样的问题,我也不得不将它包装在 MaterialApp 中,但我用其他方式做到了,没有使用 MediaQuery。就我而言,它有效
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();
});
}
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
)
}
我也遇到了同样的问题,用下面的方法解决了。
注意:我使用的是 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);
});
}
在测试环境中用 MaterialApp
包装您的小部件。
替换为:
await tester.pumpWidget(HomeScreen());
与:
await tester.pumpWidget(MaterialApp(home:HomeScreen()));
替换这个
void main() {
runApp(const MyApp());
}
有了这个
void main() {
runApp(
const MaterialApp(
home: MyApp(),
),
);
}
用 MaterialApp()
包装您的小部件,并将新的 class 传递给 MaterialApp()
小部件的 home
属性。
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
);
}
}
参考自:How to Solve ’No MediaQuery widget found’ Error in Flutter
包装您的主要方法
由此
void main() {
runApp( YourScreen(),
);
}
到
void main() {
runApp(
MaterialApp(
home: LoginScreen(),
),
);
}
我的完整代码
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(""),
),
);
}
}
我已经在 Flutter 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);
});
它工作正常..