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

时间:2018-06-05 16:13:12

标签: dart flutter flutter-test

比方说,我使用WidgetTester对Flutter中的屏幕进行了测试。有一个按钮,通过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)
            )
        )
    );
  }

}

测试

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'。这很容易解决,你不需要指出它。

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

4 个答案:

答案 0 :(得分:15)

虽然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 :(得分:4)

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

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
  <button class="button">Submit</button>
</form>

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

答案 2 :(得分:1)

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

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

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

导航抽象

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

注入小部件

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

}

测试示例(使用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());
  });
}

答案 3 :(得分:0)

为什么不直接使用 npx react-native init HelloRN

Navigator

在你的测试中,它应该是这样的:

Widget bindWithNavigator(Widget widget) {
  // Navigator dependency
  return MaterialApp(
    home: Navigator(
      onPopPage: (route, result) => route.didPop(result),
      pages: [
        MaterialPage(child: Container()),
        MaterialPage(child: widget),
      ],
    ),
  );
}

void main() { testWidgets('should pop', (WidgetTester tester) async { await tester.pumpWidget(bindWithNavigator(MyWidget())); await tester.tap(find.byIcon(Icons.back)); await tester.pumpAndSettle(); expect(find.byIcon(Icons.back), findsNothing); }); } 中应该是一样的。