Home Articles

如何在Flutter中通过Navigator测试导航

Asked
Viewed 255 times
2

比方说,我使用 WidgetTester 对Flutter中的屏幕进行了测试 . 有一个按钮,通过 Navigator 执行导航 . 我想测试该按钮的行为 .

Widget/Screen

class MyScreen extends StatefulWidget {

  MyScreen({Key key}) : super(key: key);

  @override
  _MyScreenState createState() => _MyScreenScreenState();
}

class _MyScreenState extends State<MyScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: RaisedButton(
                onPressed: () {
                    Navigator.of(context).pushNamed("/nextscreen");
                },
                child: Text(Strings.traktTvUrl)
            )
        )
    );
  }

}

Test

void main() {

  testWidgets('Button is present and triggers navigation after tapped',
      (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(home: MyScreen()));
    expect(find.byType(RaisedButton), findsOneWidget);
    await tester.tap(find.byType(RaisedButton));
    //how to test navigator?    
  });
}

我有一个正确的方法如何检查,导航器被调用?或者有没有办法模拟和替换导航器?

请注意,上面的代码实际上会因异常而失败,因为在应用程序中没有声明的命名路由 '/nextscreen' . 那个's simple to solve and you don'需要指出来 .

我主要关心的是如何在Flutter中正确地处理这个测试场景 .

3 Answers

  • 8

    以下解决方案是,一般的方法,而不是特定于Flutter .

    导航可以从屏幕或小部件中抽象出来 . 测试可以模拟并注入此抽象 . 这种方法应该足以测试这种行为 .

    有几种方法可以实现这一目标 . 为了回应这个问题,我将展示其中一个 . 也许有可能简化它或使它更“Darty” .

    Abstraction for navigation

    class AppNavigatorFactory {
      AppNavigator get(BuildContext context) =>
          AppNavigator._forNavigator(Navigator.of(context));
    }
    
    class TestAppNavigatorFactory extends AppNavigatorFactory {
      final AppNavigator mockAppNavigator;
    
      TestAppNavigatorFactory(this.mockAppNavigator);
    
      @override
      AppNavigator get(BuildContext context) => mockAppNavigator;
    }
    
    class AppNavigator {
      NavigatorState _flutterNavigator;
      AppNavigator._forNavigator(this._flutterNavigator);
    
      void showNextscreen() {
        _flutterNavigator.pushNamed('/nextscreen');
      }
    }
    

    Injection into a widget

    class MyScreen extends StatefulWidget {
      final _appNavigatorFactory;
      MyScreen(this._appNavigatorFactory, {Key key}) : super(key: key);
    
      @override
      _MyScreenState createState() => _MyScreenScreenState(_appNavigatorFactory);
    }
    
    class _MyScreenState extends State<MyScreen> {
      final _appNavigatorFactory;
    
      _MyScreenState(this._appNavigatorFactory);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: Center(
                child: RaisedButton(
                    onPressed: () {
                        _appNavigatorFactory.get(context).showNextscreen();
                    },
                    child: Text(Strings.traktTvUrl)
                )
            )
        );
      }
    
    }
    

    Example of a test (使用Mockito for Dart

    class MockAppNavigator extends Mock implements AppNavigator {}
    
    void main() {
      final appNavigator = MockAppNavigator();
    
      setUp(() {
        reset(appNavigator);
      });
    
    
      testWidgets('Button is present and triggers navigation after tapped',
          (WidgetTester tester) async {
    
        await tester.pumpWidget(MaterialApp(home: MyScreen(TestAppNavigatorFactory())));
    
        expect(find.byType(RaisedButton), findsOneWidget);
        await tester.tap(find.byType(RaisedButton));
    
        verify(appNavigator.showNextscreen());
      });
    }
    
  • 2

    虽然Danny所说的是正确且有效的,但你也可以创建一个模拟的NavigatorObserver来避免任何额外的样板:

    class MockNavigatorObserver extends Mock implements NavigatorObserver {}
    

    这将转换为您的测试用例如下:

    void main() {
      testWidgets('Button is present and triggers navigation after tapped',
          (WidgetTester tester) async {
        final mockObserver = MockNavigatorObserver();
        await tester.pumpWidget(
          MaterialApp(
            home: MyScreen(),
            navigatorObservers: [mockObserver],
          ),
        );
    
        expect(find.byType(RaisedButton), findsOneWidget);
        await tester.tap(find.byType(RaisedButton));
        await tester.pumpAndSettle();
    
        /// Verify that a push event happened
        verify(mockObserver.didPush(typed(any), typed(any)));
    
        /// You'd also want to be sure that your page is now
        /// present in the screen.
        expect(find.byType(DetailsPage), findsOneWidget);
      });
    }
    

    我在我的博客which you can find here上写了一篇深入的文章 .

  • 1

    navigator tests in the flutter repo中,他们使用NavigatorObserver类来观察导航:

    class TestObserver extends NavigatorObserver {
      OnObservation onPushed;
      OnObservation onPopped;
      OnObservation onRemoved;
      OnObservation onReplaced;
    
      @override
      void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
        if (onPushed != null) {
          onPushed(route, previousRoute);
        }
      }
    
      @override
      void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
        if (onPopped != null) {
          onPopped(route, previousRoute);
        }
      }
    
      @override
      void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
        if (onRemoved != null)
          onRemoved(route, previousRoute);
      }
    
      @override
      void didReplace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
        if (onReplaced != null)
          onReplaced(newRoute, oldRoute);
      }
    }
    

    这看起来应该做你想要的,但是它可能只能在顶级(MaterialApp)工作,我不确定你是否可以只将它提供给一个小部件 .

Related