簡體   English   中英

如何為使用連接插件的小部件編寫 Flutter 小部件測試

[英]How to write Flutter widget test for a widget that uses the Connectivity plugin

我有一個使用 Flutter 開發團隊的連接package 的屏幕。 當我不使用連接 package 時,我能夠為此小部件構建黃金圖像,但是當我按照 Flutter 開發團隊在其 pub.dev 頁面上概述的方式添加它時,我在運行測試時遇到異常通過flutter test --update-goldens命令。

I have included the test file ( test/widget/widget_test.dart ), main.dart , welcome_screen.dart , and the output from running the tests. 我嘗試尋找其他人在網上遇到過的類似問題,但我的努力沒有結果; 我正在尋求解決此問題的幫助。 任何意見或建議將不勝感激!

Output

來自flutter test --update-goldens

══╡ EXCEPTION CAUGHT BY SERVICES LIBRARY ╞══════════════════════════════════════════════════════════
The following MissingPluginException was thrown while activating platform stream on channel
plugins.flutter.io/connectivity_status:
MissingPluginException(No implementation found for method listen on channel
plugins.flutter.io/connectivity_status)

When the exception was thrown, this was the stack:
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following MissingPluginException was thrown running a test:
MissingPluginException(No implementation found for method check on channel
plugins.flutter.io/connectivity)

When the exception was thrown, this was the stack:
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...

The test description was:
  WelcomeScreen Golden test
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following message was thrown:
Multiple exceptions (2) were detected during the running of the current test, and at least one was
unexpected.
════════════════════════════════════════════════════════════════════════════════════════════════════
00:04 +14 -1: /Users/---/Documents/---/---/---/test/widget/widget_test.dart: WelcomeScreen Golden test [E]                                                                                                                      
  Test failed. See exception logs above.
  The test description was: WelcomeScreen Golden test
  
00:04 +14 -1: Some tests failed.

測試:widget_test.dart

void main() {
  testWidgets('WelcomeScreen Golden test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    await expectLater(
      find.byType(MyApp),
      matchesGoldenFile('main.png'),
    );
  });
}

main.dart

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusManager.instance.primaryFocus.unfocus();
      },
      child: MaterialApp(
        initialRoute: WelcomeScreen.id,
        routes: {
          WelcomeScreen.id: (context) => WelcomeScreen(),
          DashboardScreen.id: (context) => DashboardScreen(),
        },
      ),
    );
  }
}

屏幕:welcome_screen.dart

class WelcomeScreen extends StatefulWidget {
  static const String id = 'welcome_screen';

  @override
  _WelcomeScreenState createState() => _WelcomeScreenState();
}

class _WelcomeScreenState extends State<WelcomeScreen> {
  
  ConnectivityResult _connectionStatus = ConnectivityResult.none;
  final Connectivity _connectivity = Connectivity();
  StreamSubscription<ConnectivityResult> _connectivitySubscription;

  String username = '';
  String password = '';
  bool isLoading = false;

  @override
  void initState() {
    super.initState();

    _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
    initConnectivity();
  }

  @override
  void dispose() {
    _connectivitySubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Image.asset('images/logo.png'),
            RoundedTextField(
              textInputAction: TextInputAction.next,
              placeholder: 'Username',
              icon: Icons.person,
              color: Colors.lightBlueAccent,
              onChanged: (value) {
                setState(() {
                  username = value;
                });
              },
            ),
            RoundedTextField(
              textInputAction: TextInputAction.done,
              placeholder: 'Password',
              icon: Icons.lock,
              color: Colors.lightBlueAccent,
              password: true,
              onChanged: (value) {
                setState(() {
                  password = value;
                });
              },
            ),
            isLoading
                ? Center(
                    child: Padding(
                      padding: EdgeInsets.symmetric(vertical: 22.0),
                      child: CircularProgressIndicator(),
                    ),
                  )
                : RoundedButton(
                    disabled: isLoading,
                    title: 'Log In',
                    color: Colors.lightBlueAccent,
                    onPressed: (_connectionStatus == ConnectivityResult.mobile || _connectionStatus == ConnectivityResult.wifi)
                        ? () async {
                            setState(() {
                              isLoading = true;
                            });
                            try {
                              Login login = await API().login(username, password);
                              if (login.appUserKey != 0) {
                                Navigator.pushNamed(context, DashboardScreen.id);
                              }
                            } catch (e) {
                              print(e);
                            }
                            setState(() {
                              isLoading = false;
                            });
                          }
                        : null,
                  ),
          ],
        ),
      ),
    );
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initConnectivity() async {
    ConnectivityResult result;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      result = await _connectivity.checkConnectivity();
    } on PlatformException catch (e) {
      print(e.toString());
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) {
      return Future.value(null);
    }

    return _updateConnectionStatus(result);
  }

  Future<void> _updateConnectionStatus(ConnectivityResult result) async {
    switch (result) {
      case ConnectivityResult.wifi:
      case ConnectivityResult.mobile:
        setState(() => _connectionStatus = result);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Connected to network'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
      case ConnectivityResult.none:
        setState(() => _connectionStatus = result);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Disconnected from network'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
      default:
        setState(() => _connectionStatus = null);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Connectivity failed'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
    }
  }
}

我解決了我的問題。

小部件正在嘗試運行在運行測試的平台上不存在的方法。 我決定模擬連接 class,將模擬的 class 發送到小部件,然后讓小部件本身檢查什么類型的 class 以確定它是否應該嘗試運行它收到的監聽方法; 該方法的調用先前已導致引發異常。 我使用Mockito package 輕松模擬該服務。

我已經包含了為我解決問題的相關代碼片段。

測試:widget_test.dart

重要提示:使用Mockito模擬連接 class

class MockConnectivity extends Mock implements Connectivity {}

void main() {
  testWidgets('WelcomeScreen', (WidgetTester tester) async {
    await tester.runAsync(() async {
      await tester.pumpWidget(
        MaterialApp(
          home: WelcomeScreen(
            connectivity: MockConnectivity(),
          ),
        ),
      );
      await tester.pumpAndSettle();
    });

    await expectLater(
      find.byType(WelcomeScreen),
      matchesGoldenFile('welcome_screen_portrait.png'),
    );
  });
}

main.dart

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: WelcomeScreen.id,
      routes: {
        WelcomeScreen.id: (context) => WelcomeScreen(
          connectivity: Connectivity(),
        ),
      },
    );
  }
}

Welcome_screen.dart

class WelcomeScreen extends StatefulWidget {
  static const String id = 'welcome_screen';
  final Connectivity connectivity;
  WelcomeScreen({this.connectivity});

  @override
  _WelcomeScreenState createState() => _WelcomeScreenState();
}

class _WelcomeScreenState extends State<WelcomeScreen> {
  Connectivity _connectivity;
  ConnectivityResult _connectionStatus = ConnectivityResult.none;
  StreamSubscription<ConnectivityResult> _connectivitySubscription;

  @override
  void initState() {
    super.initState();

    _connectivity = widget.connectivity;
    if (_connectivity.runtimeType == Connectivity) {
      _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
    }
    initConnectivity();
  }

  @override
  void dispose() {
    controller.dispose();
    if (_connectivity.runtimeType == Connectivity) {
      _connectivitySubscription.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text('Welcome'),
    );
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initConnectivity() async {
    ConnectivityResult result;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      result = await _connectivity.checkConnectivity();
    } on PlatformException catch (e) {
      print(e.toString());
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) {
      return Future.value(null);
    }

    return _updateConnectionStatus(result);
  }

  Future<void> _updateConnectionStatus(ConnectivityResult result) async {
    switch (result) {
      case ConnectivityResult.wifi:
      case ConnectivityResult.mobile:
        setState(() => _connectionStatus = result);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Connected to network'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
      case ConnectivityResult.none:
        setState(() => _connectionStatus = result);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Disconnected from network'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
      default:
        setState(() => _connectionStatus = null);
        ScaffoldMessenger.of(context).hideCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Connectivity failed'),
            duration: Duration(seconds: 1),
          ),
        );
        break;
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM