Как проверить навигацию через Навигатор во Флаттере - PullRequest
0 голосов
/ 05 июня 2018

Допустим, у меня есть тест для экрана во 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)
            )
        )
    );
  }

}

Тест

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.

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Следующее решение, скажем, общий подход, и оно не является специфичным для Flutter.

Навигация может быть удалена от экрана или виджета.Тест может издеваться и внедрять эту абстракцию.Такой подход должен быть достаточным для проверки такого поведения.

Есть несколько способов, как этого добиться.Я покажу один из них, с целью этого ответа.Возможно, можно немного упростить его или сделать его более «дартичным».

Абстракция для навигации

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 для дротика )

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());
  });
}
0 голосов
/ 23 августа 2018

Хотя то, что сказал Дэнни, правильно и работает, вы также можете создать поддельный 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);
  });
}

Я написал подробную статью об этом в своем блоге , которую вы можете найти здесь .

0 голосов
/ 05 июня 2018

В тестах навигатора в репозитории флаттера они используют класс 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), я не уверен, что вы можете предоставить его только виджету.

...