【问题标题】:Widget test fails with No MediaQuery widget found小部件测试失败,未找到任何 MediaQuery 小部件
【发布时间】:2018-07-07 23:57:04
【问题描述】:

我的问题是关于颤振小部件测试,测试现有小部件包装新 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 作为参数。

【问题讨论】:

    标签: flutter dart flutter-test


    【解决方案1】:

    包装你的主要方法 从此

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

    【讨论】:

      【解决方案2】:

      MaterialApp() 包装您的小部件,并将新类传递给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

      【讨论】:

        【解决方案3】:

        替换这个

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

        有了这个

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

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

            在测试环境中使用MaterialApp 包装您的小部件。

            替换这个:

            await tester.pumpWidget(HomeScreen());
            

            与:

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

            【讨论】:

              【解决方案6】:

              我也遇到了同样的问题,用下面的方法解决了。

              注意:我使用的是 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);
               });
              } 
              

              【讨论】:

                【解决方案7】:

                我遇到了同样的问题,我还必须将它包装在 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();
                
                });
                }
                

                【讨论】:

                  【解决方案8】:

                  您需要用MediaQuery(...) 实例包装您的小部件,并且因为您使用的是Scaffold(..),所以您必须将它包装在MaterialApp(..)

                  Read more about MediaQuery

                  示例:

                  Widget testWidget = new MediaQuery(
                        data: new MediaQueryData(),
                        child: new MaterialApp(home: new LoginForm())
                  )
                  

                  【讨论】:

                  • 您的解决方案比下面@nknezevic 提出的解决方案有优势吗? MediaQuery 的文档指出 MaterialApp 和 WidgetsApp 引入了 MediaQuery。我是菜鸟,所以很想了解。否则,下面的答案似乎更可取,因为它是一个更清洁、更简单的解决方案,imo。
                  • 或者只是简单地用 MaterialApp 包装 LoginForm 。那应该像 testWidget = MaterialApp(home: LoginForm());
                  猜你喜欢
                  • 1970-01-01
                  • 2021-10-08
                  • 2021-03-31
                  • 2019-05-23
                  • 2020-12-11
                  • 2021-12-30
                  • 1970-01-01
                  • 2020-06-29
                  • 2020-12-29
                  相关资源
                  最近更新 更多