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

Jos*_*cik 6 dart flutter flutter-test

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

空间/屏幕

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

}
Run Code Online (Sandbox Code Playgroud)

测试

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?    
  });
}
Run Code Online (Sandbox Code Playgroud)

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

请注意,上面的代码实际上会因异常而失败,因为'/nextscreen'在应用程序中没有声明命名的路由.这很容易解决,你不需要指出它.

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

Iir*_*kka 27

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

class MockNavigatorObserver extends Mock implements NavigatorObserver {}
Run Code Online (Sandbox Code Playgroud)

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

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);
  });
}
Run Code Online (Sandbox Code Playgroud)

我在博客上写了一篇关于这个的深入文章,你可以在这里找到.

  • `参数类型'Null'不能分配给参数类型'Route&lt;dynamic&gt;'` (19认同)
  • 除了推送事件之外,还有其他方式可以让“DetailsPage”变得可见。`MyScreen` 可能会意外地调用 `.pushReplacement()`。这会将“MyScreen”替换为“DetailsPage”,并且将无法再返回“MyScreen”。这可能不是预期的行为。 (2认同)

Dan*_*eny 5

flutter仓库中导航器测试中,他们使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

看起来它应该可以满足您的要求,但是它只能在顶层(MaterialApp)上运行,我不确定是否可以仅将其提供给小部件。

  • 这绝对是一种方法,谢谢您指出。不过,我觉得如果经常使用的话,使用起来会有点繁琐,并且在测试中很难阅读。也许我可以尝试找到一些聪明的方法将其隐藏在一些助手或语法糖后面。或者我可能只是将 Navigator 封装在自定义抽象中并对其进行模拟。 (2认同)