たとえば、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でこのテストシナリオに正しくアプローチする方法です。
フラッターリポジトリのナビゲーターテスト では、ナビゲーションを監視するために 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)からしか機能しないかもしれません。ウィジェットだけに提供できるかどうかはわかりません。
ダニーが言ったことは正しく機能していますが、偽の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);
});
}
私はこれについてブログで詳細な記事を書きました ここで見つけることができます 。
次の解決策は、一般的なアプローチであり、Flutterに固有のものではありません。
画面またはウィジェットからナビゲーションを抽象化することができます。テストはこの抽象化をモックして注入することができます。このアプローチは、そのような動作をテストするのに十分なはずです。
それを達成する方法はいくつかあります。この応答のために、そのうちの1つを示します。おそらくそれを少し単純化するか、より「ダーティ」にすることが可能です。
ナビゲーションの抽象化
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() => _MyScreenState(_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)
)
)
);
}
}
テストの例(使用 Dartの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());
});
}